| // Copyright 2015 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/public/browser/push_messaging_service.h" |
| |
| #include <stdint.h> |
| |
| #include <optional> |
| #include <string> |
| #include <string_view> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/command_line.h" |
| #include "base/functional/bind.h" |
| #include "base/run_loop.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/test/bind.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/time/time.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/content_settings/host_content_settings_map_factory.h" |
| #include "chrome/browser/gcm/gcm_profile_service_factory.h" |
| #include "chrome/browser/history/history_service_factory.h" |
| #include "chrome/browser/permissions/permission_manager_factory.h" |
| #include "chrome/browser/push_messaging/push_messaging_app_identifier.h" |
| #include "chrome/browser/push_messaging/push_messaging_features.h" |
| #include "chrome/browser/push_messaging/push_messaging_service_factory.h" |
| #include "chrome/browser/push_messaging/push_messaging_service_impl.h" |
| #include "chrome/browser/push_messaging/push_messaging_utils.h" |
| #include "chrome/test/base/testing_browser_process.h" |
| #include "chrome/test/base/testing_profile.h" |
| #include "components/content_settings/core/browser/host_content_settings_map.h" |
| #include "components/gcm_driver/crypto/gcm_crypto_test_helpers.h" |
| #include "components/gcm_driver/fake_gcm_client_factory.h" |
| #include "components/gcm_driver/fake_gcm_profile_service.h" |
| #include "components/gcm_driver/gcm_profile_service.h" |
| #include "components/history/core/browser/history_database_params.h" |
| #include "components/history/core/browser/history_service.h" |
| #include "components/history/core/test/test_history_database.h" |
| #include "components/permissions/permission_manager.h" |
| #include "content/public/browser/permission_result.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/mojom/push_messaging/push_messaging_status.mojom.h" |
| |
| #if BUILDFLAG(ENABLE_EXTENSIONS) |
| #include "chrome/browser/extensions/chrome_test_extension_loader.h" |
| #include "chrome/browser/extensions/extension_browsertest.h" |
| #include "chrome/browser/extensions/extension_service_test_with_install.h" |
| #include "content/public/browser/permission_result.h" |
| #include "content/public/test/mock_permission_controller.h" |
| #include "extensions/common/extension.h" |
| #include "extensions/test/test_extension_dir.h" |
| #endif // BUILDFLAG(ENABLE_EXTENSIONS) |
| |
| #if BUILDFLAG(IS_ANDROID) |
| #include "components/gcm_driver/instance_id/instance_id_android.h" |
| #include "components/gcm_driver/instance_id/scoped_use_fake_instance_id_android.h" |
| #include "components/prefs/testing_pref_service.h" |
| #include "content/public/browser/permission_controller.h" |
| #include "content/public/browser/permission_descriptor_util.h" |
| #include "third_party/blink/public/common/permissions/permission_utils.h" |
| #include "url/gurl.h" |
| #include "url/origin.h" |
| #endif // BUILDFLAG(IS_ANDROID) |
| |
| namespace { |
| |
| constexpr std::string_view kTestOrigin = "https://example.com"; |
| constexpr std::string_view kTestSenderId = "1234567890"; |
| constexpr int64_t kTestServiceWorkerId = 42; |
| constexpr std::string_view kTestPayload = "Hello, world!"; |
| |
| // NIST P-256 public key in uncompressed format per SEC1 2.3.3. |
| constexpr uint8_t kTestP256Key[] = { |
| 0x04, 0x55, 0x52, 0x6A, 0xA5, 0x6E, 0x8E, 0xAA, 0x47, 0x97, 0x36, |
| 0x10, 0xC1, 0x66, 0x3C, 0x1E, 0x65, 0xBF, 0xA1, 0x7B, 0xEE, 0x48, |
| 0xC9, 0xC6, 0xBB, 0xBF, 0x02, 0x18, 0x53, 0x72, 0x1D, 0x0C, 0x7B, |
| 0xA9, 0xE3, 0x11, 0xB7, 0x03, 0x52, 0x21, 0xD3, 0x71, 0x90, 0x13, |
| 0xA8, 0xC1, 0xCF, 0xED, 0x20, 0xF7, 0x1F, 0xD1, 0x7F, 0xF2, 0x76, |
| 0xB6, 0x01, 0x20, 0xD8, 0x35, 0xA5, 0xD9, 0x3C, 0x43, 0xFD}; |
| |
| static_assert(sizeof(kTestP256Key) == 65, |
| "The fake public key must be a valid P-256 uncompressed point."); |
| |
| // URL-safe base64 encoded version of the |kTestP256Key|. |
| constexpr char kTestEncodedP256Key[] = |
| "BFVSaqVujqpHlzYQwWY8HmW_oXvuSMnGu78CGFNyHQx7qeMRtwNSIdNxkBOowc_tIPcf0X_ydr" |
| "YBINg1pdk8Q_0"; |
| |
| // Implementation of the TestingProfile that provides the Push Messaging Service |
| // and the Permission Manager, both of which are required for the tests. |
| class PushMessagingTestingProfile : public TestingProfile { |
| public: |
| PushMessagingTestingProfile() = default; |
| |
| PushMessagingTestingProfile(const PushMessagingTestingProfile&) = delete; |
| PushMessagingTestingProfile& operator=(const PushMessagingTestingProfile&) = |
| delete; |
| |
| ~PushMessagingTestingProfile() override = default; |
| |
| PushMessagingServiceImpl* GetPushMessagingService() override { |
| return PushMessagingServiceFactory::GetForProfile(this); |
| } |
| |
| permissions::PermissionManager* GetPermissionControllerDelegate() override { |
| return PermissionManagerFactory::GetForProfile(this); |
| } |
| }; |
| |
| std::unique_ptr<KeyedService> BuildFakeGCMProfileService( |
| content::BrowserContext* context) { |
| return gcm::FakeGCMProfileService::Build(static_cast<Profile*>(context)); |
| } |
| |
| std::unique_ptr<KeyedService> BuildTestHistoryService( |
| content::BrowserContext* context) { |
| auto service = std::make_unique<history::HistoryService>(); |
| service->Init(history::TestHistoryDatabaseParamsForPath(context->GetPath())); |
| return service; |
| } |
| |
| } // namespace |
| |
| class PushMessagingServiceTest : public ::testing::Test { |
| public: |
| PushMessagingServiceTest() { |
| // Override the GCM Profile service so that we can send fake messages. |
| gcm::GCMProfileServiceFactory::GetInstance()->SetTestingFactory( |
| &profile_, base::BindRepeating(&BuildFakeGCMProfileService)); |
| |
| HistoryServiceFactory::GetInstance()->SetTestingFactory( |
| &profile_, base::BindRepeating(&BuildTestHistoryService)); |
| } |
| |
| ~PushMessagingServiceTest() override = default; |
| |
| void SetUp() override { |
| TestingBrowserProcess::GetGlobal()->CreateGlobalFeaturesForTesting(); |
| } |
| |
| void SetPermission(const GURL& origin, ContentSetting value) { |
| HostContentSettingsMap* host_content_settings_map = |
| HostContentSettingsMapFactory::GetForProfile(&profile_); |
| host_content_settings_map->SetContentSettingDefaultScope( |
| origin, origin, ContentSettingsType::NOTIFICATIONS, value); |
| } |
| |
| // Callback to use when the subscription may have been subscribed. |
| void DidRegister(std::string* subscription_id_out, |
| GURL* endpoint_out, |
| std::optional<base::Time>* expiration_time_out, |
| std::vector<uint8_t>* p256dh_out, |
| std::vector<uint8_t>* auth_out, |
| base::OnceClosure done_callback, |
| const std::string& registration_id, |
| const GURL& endpoint, |
| const std::optional<base::Time>& expiration_time, |
| const std::vector<uint8_t>& p256dh, |
| const std::vector<uint8_t>& auth, |
| blink::mojom::PushRegistrationStatus status) { |
| EXPECT_EQ(blink::mojom::PushRegistrationStatus::SUCCESS_FROM_PUSH_SERVICE, |
| status); |
| |
| *subscription_id_out = registration_id; |
| *expiration_time_out = expiration_time; |
| *endpoint_out = endpoint; |
| *p256dh_out = p256dh; |
| *auth_out = auth; |
| |
| std::move(done_callback).Run(); |
| } |
| |
| // Callback to use when observing messages dispatched by the push service. |
| void DidDispatchMessage( |
| std::string* app_id_out, |
| GURL* origin_out, |
| int64_t* service_worker_registration_id_out, |
| std::optional<std::string>* payload_out, |
| const std::string& app_id, |
| const GURL& origin, |
| int64_t service_worker_registration_id, |
| std::optional<std::string> payload, |
| PushMessagingServiceImpl::PushEventCallback callback) { |
| *app_id_out = app_id; |
| *origin_out = origin; |
| *service_worker_registration_id_out = service_worker_registration_id; |
| *payload_out = std::move(payload); |
| } |
| |
| class TestPushSubscription { |
| public: |
| std::string subscription_id_; |
| GURL endpoint_; |
| std::optional<base::Time> expiration_time_; |
| std::vector<uint8_t> p256dh_; |
| std::vector<uint8_t> auth_; |
| TestPushSubscription(const std::string& subscription_id, |
| const GURL& endpoint, |
| const std::optional<base::Time>& expiration_time, |
| const std::vector<uint8_t>& p256dh, |
| const std::vector<uint8_t>& auth) |
| : subscription_id_(subscription_id), |
| endpoint_(endpoint), |
| expiration_time_(expiration_time), |
| p256dh_(p256dh), |
| auth_(auth) {} |
| TestPushSubscription() = default; |
| }; |
| |
| void Subscribe(PushMessagingServiceImpl* push_service, |
| const GURL& origin, |
| TestPushSubscription* subscription = nullptr) { |
| std::string subscription_id; |
| GURL endpoint; |
| std::optional<base::Time> expiration_time; |
| std::vector<uint8_t> p256dh, auth; |
| |
| base::RunLoop run_loop; |
| |
| auto options = blink::mojom::PushSubscriptionOptions::New(); |
| options->user_visible_only = true; |
| options->application_server_key = std::vector<uint8_t>( |
| std::begin(kTestSenderId), std::end(kTestSenderId)); |
| |
| push_service->SubscribeFromWorker( |
| origin, kTestServiceWorkerId, /*render_process_id=*/-1, |
| std::move(options), |
| base::BindOnce(&PushMessagingServiceTest::DidRegister, |
| base::Unretained(this), &subscription_id, &endpoint, |
| &expiration_time, &p256dh, &auth, |
| run_loop.QuitClosure())); |
| |
| EXPECT_EQ(0u, subscription_id.size()); // this must be asynchronous |
| |
| run_loop.Run(); |
| |
| ASSERT_GT(subscription_id.size(), 0u); |
| ASSERT_TRUE(endpoint.is_valid()); |
| ASSERT_GT(endpoint.spec().size(), 0u); |
| ASSERT_GT(p256dh.size(), 0u); |
| ASSERT_GT(auth.size(), 0u); |
| |
| if (subscription) { |
| subscription->subscription_id_ = subscription_id; |
| subscription->endpoint_ = endpoint; |
| subscription->p256dh_ = p256dh; |
| subscription->auth_ = auth; |
| } |
| } |
| |
| protected: |
| PushMessagingTestingProfile* profile() { return &profile_; } |
| |
| content::BrowserTaskEnvironment& task_environment() { |
| return task_environment_; |
| } |
| |
| private: |
| content::BrowserTaskEnvironment task_environment_{ |
| base::test::TaskEnvironment::TimeSource::MOCK_TIME}; |
| |
| PushMessagingTestingProfile profile_; |
| |
| #if BUILDFLAG(IS_ANDROID) |
| instance_id::InstanceIDAndroid::ScopedBlockOnAsyncTasksForTesting |
| block_async_; |
| #endif // BUILDFLAG(IS_ANDROID) |
| }; |
| |
| TEST_F(PushMessagingServiceTest, RecordsRevocationAndSourceUiNoReporterTest) { |
| base::HistogramTester histograms; |
| |
| PushMessagingServiceImpl* push_service = profile()->GetPushMessagingService(); |
| ASSERT_TRUE(push_service); |
| |
| const GURL origin(kTestOrigin); |
| SetPermission(origin, CONTENT_SETTING_ALLOW); |
| |
| ASSERT_EQ(blink::mojom::PermissionStatus::GRANTED, |
| push_service->GetPermissionStatus(origin, true /* user_visible */)); |
| |
| Subscribe(push_service, origin); |
| |
| SetPermission(origin, CONTENT_SETTING_DEFAULT); |
| |
| histograms.ExpectUniqueSample( |
| "Permissions.Action.Notifications", |
| static_cast<int>(permissions::PermissionAction::REVOKED), 1); |
| histograms.ExpectUniqueSample( |
| "Permissions.Revocation.Notifications.SourceUI", |
| static_cast<int>(permissions::PermissionSourceUI::UNIDENTIFIED), 1); |
| } |
| |
| TEST_F(PushMessagingServiceTest, RecordsRevocationAndSourceUiWithReporterTest) { |
| base::HistogramTester histograms; |
| |
| PushMessagingServiceImpl* push_service = profile()->GetPushMessagingService(); |
| ASSERT_TRUE(push_service); |
| |
| const GURL origin(kTestOrigin); |
| SetPermission(origin, CONTENT_SETTING_ALLOW); |
| |
| ASSERT_EQ(blink::mojom::PermissionStatus::GRANTED, |
| push_service->GetPermissionStatus(origin, true /* user_visible */)); |
| |
| Subscribe(push_service, origin); |
| |
| const auto source_ui = permissions::PermissionSourceUI::SITE_SETTINGS; |
| { |
| permissions::PermissionUmaUtil::ScopedRevocationReporter |
| scoped_revocation_reporter(profile(), origin, origin, |
| ContentSettingsType::NOTIFICATIONS, |
| source_ui); |
| SetPermission(origin, CONTENT_SETTING_DEFAULT); |
| } |
| |
| histograms.ExpectUniqueSample( |
| "Permissions.Action.Notifications", |
| static_cast<int>(permissions::PermissionAction::REVOKED), 1); |
| histograms.ExpectUniqueSample("Permissions.Revocation.Notifications.SourceUI", |
| static_cast<int>(source_ui), 1); |
| } |
| |
| // Fails too often on Linux TSAN builder: http://crbug.com/1211350. |
| #if BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER) |
| #define MAYBE_PayloadEncryptionTest DISABLED_PayloadEncryptionTest |
| #else |
| #define MAYBE_PayloadEncryptionTest PayloadEncryptionTest |
| #endif |
| TEST_F(PushMessagingServiceTest, MAYBE_PayloadEncryptionTest) { |
| PushMessagingServiceImpl* push_service = profile()->GetPushMessagingService(); |
| ASSERT_TRUE(push_service); |
| |
| const GURL origin(kTestOrigin); |
| SetPermission(origin, CONTENT_SETTING_ALLOW); |
| |
| // (1) Make sure that |kExampleOrigin| has access to use Push Messaging. |
| ASSERT_EQ(blink::mojom::PermissionStatus::GRANTED, |
| push_service->GetPermissionStatus(origin, true /* user_visible */)); |
| |
| // (2) Subscribe for Push Messaging, and verify that we've got the required |
| // information in order to be able to create encrypted messages. |
| TestPushSubscription subscription; |
| Subscribe(push_service, origin, &subscription); |
| |
| // (3) Encrypt a message using the public key and authentication secret that |
| // are associated with the subscription. |
| |
| gcm::IncomingMessage message; |
| message.sender_id = kTestSenderId; |
| |
| ASSERT_TRUE(gcm::CreateEncryptedPayloadForTesting( |
| kTestPayload, |
| std::string_view(reinterpret_cast<char*>(subscription.p256dh_.data()), |
| subscription.p256dh_.size()), |
| std::string_view(reinterpret_cast<char*>(subscription.auth_.data()), |
| subscription.auth_.size()), |
| &message)); |
| |
| ASSERT_GT(message.raw_data.size(), 0u); |
| ASSERT_NE(kTestPayload, message.raw_data); |
| ASSERT_FALSE(message.decrypted); |
| |
| // (4) Find the app_id that has been associated with the subscription. |
| PushMessagingAppIdentifier app_identifier = |
| PushMessagingAppIdentifier::FindByServiceWorker(profile(), origin, |
| kTestServiceWorkerId); |
| |
| ASSERT_FALSE(app_identifier.is_null()); |
| |
| std::string app_id; |
| GURL dispatched_origin; |
| int64_t service_worker_registration_id; |
| std::optional<std::string> payload; |
| |
| // (5) Observe message dispatchings from the Push Messaging service, and |
| // then dispatch the |message| on the GCM driver as if it had actually |
| // been received by Google Cloud Messaging. |
| push_service->SetMessageDispatchedCallbackForTesting(base::BindRepeating( |
| &PushMessagingServiceTest::DidDispatchMessage, base::Unretained(this), |
| &app_id, &dispatched_origin, &service_worker_registration_id, &payload)); |
| |
| gcm::FakeGCMProfileService* fake_profile_service = |
| static_cast<gcm::FakeGCMProfileService*>( |
| gcm::GCMProfileServiceFactory::GetForProfile(profile())); |
| |
| fake_profile_service->DispatchMessage(app_identifier.app_id(), message); |
| |
| base::RunLoop().RunUntilIdle(); |
| |
| // (6) Verify that the message, as received by the Push Messaging Service, has |
| // indeed been decrypted by the GCM Driver, and has been forwarded to the |
| // Service Worker that has been associated with the subscription. |
| EXPECT_EQ(app_identifier.app_id(), app_id); |
| EXPECT_EQ(origin, dispatched_origin); |
| EXPECT_EQ(service_worker_registration_id, kTestServiceWorkerId); |
| |
| EXPECT_TRUE(payload); |
| EXPECT_EQ(kTestPayload, *payload); |
| } |
| |
| TEST_F(PushMessagingServiceTest, NormalizeSenderInfo) { |
| PushMessagingServiceImpl* push_service = profile()->GetPushMessagingService(); |
| ASSERT_TRUE(push_service); |
| |
| std::string p256dh(std::begin(kTestP256Key), std::end(kTestP256Key)); |
| ASSERT_EQ(65u, p256dh.size()); |
| |
| // NIST P-256 public keys in uncompressed format will be encoded using the |
| // URL-safe base64 encoding by the normalization function. |
| EXPECT_EQ(kTestEncodedP256Key, push_messaging::NormalizeSenderInfo(p256dh)); |
| |
| // Any other value, binary or not, will be passed through as-is. |
| EXPECT_EQ("1234567890", push_messaging::NormalizeSenderInfo("1234567890")); |
| EXPECT_EQ("foo@bar.com", push_messaging::NormalizeSenderInfo("foo@bar.com")); |
| |
| p256dh[0] = 0x05; // invalidate |p256dh| as a public key. |
| |
| EXPECT_EQ(p256dh, push_messaging::NormalizeSenderInfo(p256dh)); |
| } |
| |
| // Fails too often on Linux TSAN builder: http://crbug.com/1211350. |
| #if BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER) |
| #define MAYBE_RemoveExpiredSubscriptions DISABLED_RemoveExpiredSubscriptions |
| #else |
| #define MAYBE_RemoveExpiredSubscriptions RemoveExpiredSubscriptions |
| #endif |
| TEST_F(PushMessagingServiceTest, MAYBE_RemoveExpiredSubscriptions) { |
| // (1) Enable push subscriptions with expiration time and |
| // `pushsubscriptionchange` events |
| base::test::ScopedFeatureList scoped_feature_list_; |
| scoped_feature_list_.InitWithFeatures( |
| /* enabled features */ |
| {features::kPushSubscriptionWithExpirationTime, |
| features::kPushSubscriptionChangeEventOnInvalidation}, |
| /* disabled features */ |
| {}); |
| |
| const GURL origin(kTestOrigin); |
| SetPermission(origin, CONTENT_SETTING_ALLOW); |
| |
| // (2) Set up push service and test origin |
| PushMessagingServiceImpl* push_service = profile()->GetPushMessagingService(); |
| ASSERT_TRUE(push_service); |
| |
| // (3) Subscribe origin to push service and find corresponding |
| // |app_identifier| |
| Subscribe(push_service, origin); |
| PushMessagingAppIdentifier app_identifier = |
| PushMessagingAppIdentifier::FindByServiceWorker(profile(), origin, |
| kTestServiceWorkerId); |
| ASSERT_FALSE(app_identifier.is_null()); |
| |
| // (4) Manually set the time as expired, save the time in preferences |
| app_identifier.set_expiration_time(base::Time::UnixEpoch()); |
| app_identifier.PersistToPrefs(profile()); |
| ASSERT_EQ(1u, PushMessagingAppIdentifier::GetCount(profile())); |
| |
| // (3) Remove all expired subscriptions |
| base::RunLoop run_loop; |
| push_service->SetRemoveExpiredSubscriptionsCallbackForTesting( |
| run_loop.QuitClosure()); |
| push_service->RemoveExpiredSubscriptions(); |
| run_loop.Run(); |
| |
| // (5) We expect the subscription to be deleted |
| ASSERT_EQ(0u, PushMessagingAppIdentifier::GetCount(profile())); |
| PushMessagingAppIdentifier deleted_identifier = |
| PushMessagingAppIdentifier::FindByAppId(profile(), |
| app_identifier.app_id()); |
| EXPECT_TRUE(deleted_identifier.is_null()); |
| } |
| |
| // Tests that extensions are permitted to pass userVisibleOnly true or false |
| // when subscribing to push messages. |
| #if BUILDFLAG(ENABLE_EXTENSIONS) |
| namespace extensions { |
| |
| using ContextType = extensions::browser_test_util::ContextType; |
| |
| class ExtensionsPushMessagingServiceTest |
| : public ExtensionServiceTestWithInstall, |
| public testing::WithParamInterface<ContextType> { |
| public: |
| ExtensionsPushMessagingServiceTest() = default; |
| |
| ExtensionsPushMessagingServiceTest( |
| const ExtensionsPushMessagingServiceTest&) = delete; |
| ExtensionsPushMessagingServiceTest& operator=( |
| const ExtensionsPushMessagingServiceTest&) = delete; |
| |
| void SetUp() override { |
| TestingBrowserProcess::GetGlobal()->CreateGlobalFeaturesForTesting(); |
| ExtensionServiceTestWithInstall::SetUp(); |
| InitializeExtensionService(ExtensionServiceInitParams()); |
| } |
| }; |
| |
| // Tests that extension origins with various background contexts have permission |
| // to the Push API. It tests that they can request userVisible:true when |
| // subscribing to push notifications. Only worker based extensions are allowed |
| // to set userVisible:false though. |
| TEST_P(ExtensionsPushMessagingServiceTest, PushMessagingAPIPermission) { |
| static constexpr char kManifestPersistentBackgroundScript[] = |
| R"({"scripts": ["background.js"], "persistent": true})"; |
| static constexpr char kManifestEventPageBackgroundScript[] = |
| R"({"persistent": false, |
| "scripts": ["background.js"] |
| } |
| )"; |
| static constexpr char kManifestServiceWorkerBackgroundScript[] = |
| R"({"service_worker": "background.js"})"; |
| |
| // Load an extension of ContextType. |
| TestExtensionDir test_dir; |
| constexpr char kManifest[] = |
| R"({ |
| "name": "Test Extension", |
| "manifest_version": %s, |
| "version": "0.1", |
| "background": %s, |
| "permissions": ["notifications"] |
| })"; |
| ContextType extension_context_type = GetParam(); |
| bool worker_extension = extension_context_type == ContextType::kServiceWorker; |
| const char* background_script; |
| if (worker_extension) { |
| background_script = kManifestServiceWorkerBackgroundScript; |
| } else if (extension_context_type == ContextType::kEventPage) { |
| background_script = kManifestEventPageBackgroundScript; |
| } else { |
| background_script = kManifestPersistentBackgroundScript; |
| } |
| const char* manifest_version = worker_extension ? "3" : "2"; |
| std::string manifest = |
| base::StringPrintf(kManifest, manifest_version, background_script); |
| test_dir.WriteManifest(manifest); |
| test_dir.WriteFile(FILE_PATH_LITERAL("background.js"), ""); |
| ChromeTestExtensionLoader loader(profile()); |
| scoped_refptr<const Extension> extension = |
| loader.LoadExtension(test_dir.UnpackedPath()); |
| ASSERT_TRUE(extension); |
| |
| // Assume (with mocking) that notifications permission is granted for |
| // extensions per the manifest (for workers and non-workers). |
| auto mock_permission_controller = |
| std::make_unique<content::MockPermissionController>(); |
| auto permission_status_mock_return = content::PermissionStatus::GRANTED; |
| EXPECT_CALL(*mock_permission_controller, |
| GetPermissionStatusForWorker(testing::_, testing::_, testing::_)) |
| .WillRepeatedly(testing::Return(permission_status_mock_return)); |
| EXPECT_CALL(*mock_permission_controller, |
| GetPermissionStatusForWorker(testing::_, testing::_, testing::_)) |
| .WillRepeatedly(testing::Return(permission_status_mock_return)); |
| auto permission_result_mock_return = |
| content::PermissionResult(content::PermissionStatus::GRANTED, |
| content::PermissionStatusSource::UNSPECIFIED); |
| EXPECT_CALL(*mock_permission_controller, |
| GetPermissionResultForOriginWithoutContext(testing::_, testing::_, |
| testing::_)) |
| .WillRepeatedly(testing::Return(permission_result_mock_return)); |
| EXPECT_CALL( |
| *mock_permission_controller, |
| GetPermissionResultForOriginWithoutContext(testing::_, testing::_)) |
| .WillRepeatedly(testing::Return(permission_result_mock_return)); |
| browser_context()->SetPermissionControllerForTesting( |
| std::move(mock_permission_controller)); |
| |
| PushMessagingServiceImpl* push_service = |
| PushMessagingServiceFactory::GetForProfile(profile()); |
| ASSERT_TRUE(push_service); |
| const GURL extension_origin = |
| Extension::GetBaseURLFromExtensionId(extension->id()); |
| |
| // All extension origins (worker or non-worker) can always use the Push API if |
| // they set userVisibleOnly:true on push subscription. |
| EXPECT_EQ( |
| blink::mojom::PermissionStatus::GRANTED, |
| push_service->GetPermissionStatus(extension_origin, /*user_visible=*/ |
| true)); |
| |
| // Extension origins with workers can set userVisible:false when subscribing |
| // and still use the Push API if they don't intend to show notifications |
| // (if they try to show notifications then the permission is still enforced). |
| if (worker_extension) { |
| EXPECT_EQ( |
| blink::mojom::PermissionStatus::GRANTED, |
| push_service->GetPermissionStatus(extension_origin, /*user_visible=*/ |
| false)); |
| } else { |
| // Extension origins that are not worker based are not allowed to use the |
| // Push API if they set userVisibleOnly:false on subscription. |
| EXPECT_EQ( |
| blink::mojom::PermissionStatus::DENIED, |
| push_service->GetPermissionStatus(extension_origin, /*user_visible=*/ |
| false)); |
| } |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| NonWorkerExtension, |
| ExtensionsPushMessagingServiceTest, |
| testing::ValuesIn({ContextType::kEventPage, |
| ContextType::kPersistentBackground})); |
| INSTANTIATE_TEST_SUITE_P(WorkerBasedExtension, |
| ExtensionsPushMessagingServiceTest, |
| testing::Values(ContextType::kServiceWorker)); |
| |
| } // namespace extensions |
| |
| #endif // BUILDFLAG(ENABLE_EXTENSIONS) |
| |
| #if BUILDFLAG(IS_ANDROID) |
| class FCMRevocationTest : public PushMessagingServiceTest { |
| public: |
| FCMRevocationTest() { |
| PushMessagingServiceImpl::RegisterPrefs(prefs_.registry()); |
| } |
| |
| ~FCMRevocationTest() override = default; |
| |
| GURL GetUrl() { return origin_; } |
| |
| url::Origin GetOrigin() { return url::Origin::Create(origin_); } |
| |
| PrefService* pref() { return &prefs_; } |
| |
| void SetPermission(const GURL& origin, |
| ContentSetting value, |
| TestingProfile* profile) { |
| HostContentSettingsMap* host_content_settings_map = |
| HostContentSettingsMapFactory::GetForProfile(profile); |
| host_content_settings_map->SetContentSettingDefaultScope( |
| origin, origin, ContentSettingsType::NOTIFICATIONS, value); |
| } |
| |
| private: |
| GURL origin_ = GURL("https://example.com"); |
| TestingPrefServiceSimple prefs_; |
| }; |
| |
| TEST_F(FCMRevocationTest, ResetPrefs) { |
| content::PermissionController* permission_controller = |
| profile()->GetPermissionController(); |
| |
| const auto notification_permission_descriptor = content:: |
| PermissionDescriptorUtil::CreatePermissionDescriptorForPermissionType( |
| blink::PermissionType::NOTIFICATIONS); |
| content::PermissionResult result = |
| permission_controller->GetPermissionResultForOriginWithoutContext( |
| notification_permission_descriptor, GetOrigin()); |
| EXPECT_EQ(result.status, blink::mojom::PermissionStatus::ASK); |
| |
| SetPermission(GetUrl(), ContentSetting::CONTENT_SETTING_ALLOW, profile()); |
| |
| result = permission_controller->GetPermissionResultForOriginWithoutContext( |
| notification_permission_descriptor, GetOrigin()); |
| EXPECT_EQ(result.status, blink::mojom::PermissionStatus::GRANTED); |
| |
| const char kNotificationsPermissionRevocationGracePeriodDate[] = |
| "notifications_permission_revocation_grace_period"; |
| |
| // Just random value to make sure it is reset. |
| base::Time time = base::Time::FromTimeT(100); |
| |
| pref()->SetTime(kNotificationsPermissionRevocationGracePeriodDate, time); |
| |
| PushMessagingServiceImpl::RevokePermissionIfPossible( |
| GetUrl(), /*app_level_notifications_enabled=*/true, pref(), profile()); |
| |
| // Time is reset. |
| base::Time grace_period_date = |
| pref()->GetTime(kNotificationsPermissionRevocationGracePeriodDate); |
| EXPECT_EQ(grace_period_date, base::Time()); |
| EXPECT_NE(grace_period_date, time); |
| |
| // Permission is not reset. |
| result = permission_controller->GetPermissionResultForOriginWithoutContext( |
| notification_permission_descriptor, GetOrigin()); |
| EXPECT_EQ(result.status, blink::mojom::PermissionStatus::GRANTED); |
| } |
| |
| // This test verifies that if the grace period is not started, and there is no |
| // app-level Notifications permissions, we init the grace period prefs without |
| // revoking permissions. |
| TEST_F(FCMRevocationTest, NoAppLevelPermissionInitGracePeriodPrefsTest) { |
| content::PermissionController* permission_controller = |
| profile()->GetPermissionController(); |
| |
| const auto notification_permission_descriptor = content:: |
| PermissionDescriptorUtil::CreatePermissionDescriptorForPermissionType( |
| blink::PermissionType::NOTIFICATIONS); |
| content::PermissionResult result = |
| permission_controller->GetPermissionResultForOriginWithoutContext( |
| notification_permission_descriptor, GetOrigin()); |
| EXPECT_EQ(result.status, blink::mojom::PermissionStatus::ASK); |
| |
| SetPermission(GetUrl(), ContentSetting::CONTENT_SETTING_ALLOW, profile()); |
| |
| result = permission_controller->GetPermissionResultForOriginWithoutContext( |
| notification_permission_descriptor, GetOrigin()); |
| EXPECT_EQ(result.status, blink::mojom::PermissionStatus::GRANTED); |
| |
| const char kNotificationsPermissionRevocationGracePeriodDate[] = |
| "notifications_permission_revocation_grace_period"; |
| |
| // The grace period is not initialized. |
| EXPECT_EQ(pref()->GetTime(kNotificationsPermissionRevocationGracePeriodDate), |
| base::Time()); |
| |
| PushMessagingServiceImpl::RevokePermissionIfPossible( |
| GetUrl(), /*app_level_notifications_enabled=*/false, pref(), profile()); |
| |
| // The grace period is initialized with non-default time value. |
| EXPECT_NE(pref()->GetTime(kNotificationsPermissionRevocationGracePeriodDate), |
| base::Time()); |
| |
| // Permission is still granted. |
| result = permission_controller->GetPermissionResultForOriginWithoutContext( |
| notification_permission_descriptor, GetOrigin()); |
| EXPECT_EQ(result.status, blink::mojom::PermissionStatus::GRANTED); |
| } |
| |
| // This test verifies that if the grace period is over and there is no app-level |
| // Notifications permissions, site-level Notifications permission will be |
| // revoked. |
| TEST_F(FCMRevocationTest, NoAppLevelPermissionRevocationTest) { |
| content::PermissionController* permission_controller = |
| profile()->GetPermissionController(); |
| const auto notification_permission_descriptor = content:: |
| PermissionDescriptorUtil::CreatePermissionDescriptorForPermissionType( |
| blink::PermissionType::NOTIFICATIONS); |
| content::PermissionResult result = |
| permission_controller->GetPermissionResultForOriginWithoutContext( |
| notification_permission_descriptor, GetOrigin()); |
| EXPECT_EQ(result.status, blink::mojom::PermissionStatus::ASK); |
| |
| SetPermission(GetUrl(), ContentSetting::CONTENT_SETTING_ALLOW, profile()); |
| |
| result = permission_controller->GetPermissionResultForOriginWithoutContext( |
| notification_permission_descriptor, GetOrigin()); |
| EXPECT_EQ(result.status, blink::mojom::PermissionStatus::GRANTED); |
| |
| const char kNotificationsPermissionRevocationGracePeriodDate[] = |
| "notifications_permission_revocation_grace_period"; |
| |
| // The grace period is not initialized. |
| EXPECT_EQ(pref()->GetTime(kNotificationsPermissionRevocationGracePeriodDate), |
| base::Time()); |
| |
| // Init `time` with 4 days old time value. |
| const base::Time time = base::Time::FromDeltaSinceWindowsEpoch( |
| base::Time::Now() - |
| base::Time::FromDeltaSinceWindowsEpoch(base::Days(4))); |
| |
| // Init the grace period date with a value that is older than 3 days (the |
| // default grace period). |
| pref()->SetTime(kNotificationsPermissionRevocationGracePeriodDate, time); |
| |
| PushMessagingServiceImpl::RevokePermissionIfPossible( |
| GetUrl(), /*app_level_notifications_enabled=*/false, pref(), profile()); |
| |
| EXPECT_EQ(pref()->GetTime(kNotificationsPermissionRevocationGracePeriodDate), |
| time); |
| |
| // Permission is revoked. |
| result = permission_controller->GetPermissionResultForOriginWithoutContext( |
| notification_permission_descriptor, GetOrigin()); |
| EXPECT_EQ(result.status, blink::mojom::PermissionStatus::ASK); |
| } |
| |
| // This test verifies that if the grace period is not over and there is no |
| // app-level Notifications permissions, site-level Notifications permission will |
| // not be revoked. |
| TEST_F(FCMRevocationTest, NoAppLevelPermissionIgnoreTest) { |
| content::PermissionController* permission_controller = |
| profile()->GetPermissionController(); |
| const auto notification_permission_descriptor = content:: |
| PermissionDescriptorUtil::CreatePermissionDescriptorForPermissionType( |
| blink::PermissionType::NOTIFICATIONS); |
| content::PermissionResult result = |
| permission_controller->GetPermissionResultForOriginWithoutContext( |
| notification_permission_descriptor, GetOrigin()); |
| EXPECT_EQ(result.status, blink::mojom::PermissionStatus::ASK); |
| |
| SetPermission(GetUrl(), ContentSetting::CONTENT_SETTING_ALLOW, profile()); |
| |
| result = permission_controller->GetPermissionResultForOriginWithoutContext( |
| notification_permission_descriptor, GetOrigin()); |
| EXPECT_EQ(result.status, blink::mojom::PermissionStatus::GRANTED); |
| |
| const char kNotificationsPermissionRevocationGracePeriodDate[] = |
| "notifications_permission_revocation_grace_period"; |
| |
| // The grace period is not initialized. |
| EXPECT_EQ(pref()->GetTime(kNotificationsPermissionRevocationGracePeriodDate), |
| base::Time()); |
| |
| // Init `time` with 2 days old time value. |
| const base::Time time = base::Time::FromDeltaSinceWindowsEpoch( |
| base::Time::Now() - |
| base::Time::FromDeltaSinceWindowsEpoch(base::Days(2))); |
| // Init the grace period date with a value that is fewer than 3 days (the |
| // default grace period). |
| pref()->SetTime(kNotificationsPermissionRevocationGracePeriodDate, time); |
| |
| PushMessagingServiceImpl::RevokePermissionIfPossible( |
| GetUrl(), /*app_level_notifications_enabled=*/false, pref(), profile()); |
| |
| EXPECT_EQ(pref()->GetTime(kNotificationsPermissionRevocationGracePeriodDate), |
| time); |
| |
| // Permission is revoked. |
| result = permission_controller->GetPermissionResultForOriginWithoutContext( |
| notification_permission_descriptor, GetOrigin()); |
| EXPECT_EQ(result.status, blink::mojom::PermissionStatus::GRANTED); |
| } |
| |
| // This test verifies that if the grace period is not over and there is |
| // app-level Notifications permissions, the grace period reset will be tracked. |
| TEST_F(FCMRevocationTest, ResetAndRecordGracePeriodTest) { |
| content::PermissionController* permission_controller = |
| profile()->GetPermissionController(); |
| const auto notification_permission_descriptor = content:: |
| PermissionDescriptorUtil::CreatePermissionDescriptorForPermissionType( |
| blink::PermissionType::NOTIFICATIONS); |
| content::PermissionResult result = |
| permission_controller->GetPermissionResultForOriginWithoutContext( |
| notification_permission_descriptor, GetOrigin()); |
| EXPECT_EQ(result.status, blink::mojom::PermissionStatus::ASK); |
| |
| SetPermission(GetUrl(), ContentSetting::CONTENT_SETTING_ALLOW, profile()); |
| |
| result = permission_controller->GetPermissionResultForOriginWithoutContext( |
| notification_permission_descriptor, GetOrigin()); |
| EXPECT_EQ(result.status, blink::mojom::PermissionStatus::GRANTED); |
| |
| const char kNotificationsPermissionRevocationGracePeriodDate[] = |
| "notifications_permission_revocation_grace_period"; |
| |
| // The grace period is not initialized. |
| EXPECT_EQ(pref()->GetTime(kNotificationsPermissionRevocationGracePeriodDate), |
| base::Time()); |
| |
| // Init `time` with 2 days old time value. |
| const base::Time time = base::Time::FromDeltaSinceWindowsEpoch( |
| base::Time::Now() - |
| base::Time::FromDeltaSinceWindowsEpoch(base::Days(2))); |
| // Init the grace period date with a value that is fewer than 3 days (the |
| // default grace period). |
| pref()->SetTime(kNotificationsPermissionRevocationGracePeriodDate, time); |
| |
| PushMessagingServiceImpl::RevokePermissionIfPossible( |
| GetUrl(), /*app_level_notifications_enabled=*/true, pref(), profile()); |
| |
| EXPECT_EQ(pref()->GetTime(kNotificationsPermissionRevocationGracePeriodDate), |
| base::Time()); |
| |
| // Permission is revoked. |
| result = permission_controller->GetPermissionResultForOriginWithoutContext( |
| notification_permission_descriptor, GetOrigin()); |
| EXPECT_EQ(result.status, blink::mojom::PermissionStatus::GRANTED); |
| } |
| |
| #endif |