| // Copyright 2014 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef CHROME_BROWSER_PUSH_MESSAGING_PUSH_MESSAGING_SERVICE_IMPL_H_ |
| #define CHROME_BROWSER_PUSH_MESSAGING_PUSH_MESSAGING_SERVICE_IMPL_H_ |
| |
| #include <stdint.h> |
| |
| #include <memory> |
| #include <optional> |
| #include <queue> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/callback_list.h" |
| #include "base/containers/flat_map.h" |
| #include "base/functional/callback.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/gtest_prod_util.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/scoped_observation.h" |
| #include "base/time/time.h" |
| #include "chrome/browser/permissions/permission_revocation_request.h" |
| #include "chrome/browser/push_messaging/push_messaging_notification_manager.h" |
| #include "chrome/browser/push_messaging/push_messaging_refresher.h" |
| #include "chrome/common/buildflags.h" |
| #include "components/content_settings/core/browser/content_settings_observer.h" |
| #include "components/gcm_driver/common/gcm_message.h" |
| #include "components/gcm_driver/crypto/gcm_encryption_provider.h" |
| #include "components/gcm_driver/gcm_app_handler.h" |
| #include "components/gcm_driver/gcm_client.h" |
| #include "components/gcm_driver/instance_id/instance_id.h" |
| #include "components/keyed_service/core/keyed_service.h" |
| #include "components/safe_browsing/core/browser/db/database_manager.h" |
| #include "content/public/browser/child_process_host.h" |
| #include "content/public/browser/push_messaging_service.h" |
| #include "third_party/blink/public/mojom/push_messaging/push_messaging.mojom-forward.h" |
| |
| class GURL; |
| class PrefRegistrySimple; |
| class Profile; |
| class PushMessagingAppIdentifier; |
| class PushMessagingServiceTest; |
| class FCMRevocationTest; |
| class ScopedKeepAlive; |
| class ScopedProfileKeepAlive; |
| |
| #if BUILDFLAG(IS_ANDROID) |
| class PrefService; |
| #endif |
| |
| namespace blink { |
| namespace mojom { |
| enum class PushEventStatus; |
| enum class PushRegistrationStatus; |
| } // namespace mojom |
| } // namespace blink |
| |
| namespace content { |
| class DevToolsBackgroundServicesContext; |
| } // namespace content |
| |
| namespace gcm { |
| class GCMDriver; |
| } // namespace gcm |
| |
| namespace instance_id { |
| class InstanceIDDriver; |
| } // namespace instance_id |
| |
| namespace { |
| |
| struct PendingMessage { |
| PendingMessage(std::string app_id, gcm::IncomingMessage message); |
| PendingMessage(const PendingMessage& other); |
| PendingMessage(PendingMessage&& other); |
| ~PendingMessage(); |
| |
| PendingMessage& operator=(PendingMessage&& other); |
| |
| std::string app_id; |
| gcm::IncomingMessage message; |
| base::Time received_time; |
| }; |
| } // namespace |
| |
| class PushMessagingServiceImpl : public content::PushMessagingService, |
| public gcm::GCMAppHandler, |
| public content_settings::Observer, |
| public KeyedService, |
| public PushMessagingRefresher::Observer { |
| public: |
| // If any Service Workers are using push, starts GCM and adds an app handler. |
| static void InitializeForProfile(Profile* profile); |
| |
| explicit PushMessagingServiceImpl( |
| Profile* profile, |
| scoped_refptr<safe_browsing::SafeBrowsingDatabaseManager> |
| database_manager); |
| |
| PushMessagingServiceImpl(const PushMessagingServiceImpl&) = delete; |
| PushMessagingServiceImpl& operator=(const PushMessagingServiceImpl&) = delete; |
| |
| ~PushMessagingServiceImpl() override; |
| |
| // Check and remove subscriptions that are expired when |this| is initialized |
| void RemoveExpiredSubscriptions(); |
| |
| // Gets the permission status for the given |origin|. |
| blink::mojom::PermissionStatus GetPermissionStatus(const GURL& origin, |
| bool user_visible); |
| |
| #if BUILDFLAG(IS_ANDROID) |
| // Registers Local State prefs used by this class. |
| static void RegisterPrefs(PrefRegistrySimple* registry); |
| |
| static void RevokePermissionIfPossible(GURL origin, |
| bool app_level_notifications_enabled, |
| PrefService* prefs, |
| Profile* profile); |
| #endif |
| |
| // gcm::GCMAppHandler implementation. |
| void ShutdownHandler() override; |
| void OnStoreReset() override; |
| void OnMessage(const std::string& app_id, |
| const gcm::IncomingMessage& message) override; |
| void OnMessagesDeleted(const std::string& app_id) override; |
| void OnSendError( |
| const std::string& app_id, |
| const gcm::GCMClient::SendErrorDetails& send_error_details) override; |
| void OnSendAcknowledged(const std::string& app_id, |
| const std::string& message_id) override; |
| void OnMessageDecryptionFailed(const std::string& app_id, |
| const std::string& message_id, |
| const std::string& error_message) override; |
| bool CanHandle(const std::string& app_id) const override; |
| |
| // content::PushMessagingService implementation: |
| void SubscribeFromDocument(const GURL& requesting_origin, |
| int64_t service_worker_registration_id, |
| int render_process_id, |
| int render_frame_id, |
| blink::mojom::PushSubscriptionOptionsPtr options, |
| bool user_gesture, |
| RegisterCallback callback) override; |
| void SubscribeFromWorker(const GURL& requesting_origin, |
| int64_t service_worker_registration_id, |
| int render_process_id, |
| blink::mojom::PushSubscriptionOptionsPtr options, |
| RegisterCallback callback) override; |
| void GetSubscriptionInfo(const GURL& origin, |
| int64_t service_worker_registration_id, |
| const std::string& sender_id, |
| const std::string& subscription_id, |
| SubscriptionInfoCallback callback) override; |
| void Unsubscribe(blink::mojom::PushUnregistrationReason reason, |
| const GURL& requesting_origin, |
| int64_t service_worker_registration_id, |
| const std::string& sender_id, |
| UnregisterCallback) override; |
| bool SupportNonVisibleMessages() override; |
| void DidDeleteServiceWorkerRegistration( |
| const GURL& origin, |
| int64_t service_worker_registration_id) override; |
| void DidDeleteServiceWorkerDatabase() override; |
| |
| // content_settings::Observer implementation. |
| void OnContentSettingChanged( |
| const ContentSettingsPattern& primary_pattern, |
| const ContentSettingsPattern& secondary_pattern, |
| ContentSettingsTypeSet content_type_set) override; |
| |
| // Fires the `pushsubscriptionchange` event to the service worker with |
| // `service_worker_registration_id` and `origin`. The two subscriptions |
| // `old_subscription` and `new_subscription` can be null. |
| void FirePushSubscriptionChange( |
| const GURL& origin, |
| int64_t service_worker_registration_id, |
| base::OnceClosure completed_closure, |
| blink::mojom::PushSubscriptionPtr new_subscription, |
| blink::mojom::PushSubscriptionPtr old_subscription); |
| |
| // Fires the `pushsubscriptionchange` event to the associated service worker |
| // of |app_identifier|, which is the app identifier for |old_subscription| |
| // whereas |new_subscription| can be either null e.g. when a subscription is |
| // lost due to permission changes or a new subscription when it was refreshed. |
| void FirePushSubscriptionChangeForAppIdentifier( |
| const PushMessagingAppIdentifier& app_identifier, |
| base::OnceClosure completed_closure, |
| blink::mojom::PushSubscriptionPtr new_subscription, |
| blink::mojom::PushSubscriptionPtr old_subscription); |
| |
| // KeyedService implementation. |
| void Shutdown() override; |
| |
| // WARNING: Only call this function if features::kPushSubscriptionChangeEvent |
| // is enabled, will be later used by the Push Service to trigger subscription |
| // refreshes |
| void OnSubscriptionInvalidation(const std::string& app_id); |
| |
| // PushMessagingRefresher::Observer implementation |
| // Initiate unsubscribe task when old subscription becomes invalid |
| void OnOldSubscriptionExpired(const std::string& app_id, |
| const std::string& sender_id) override; |
| void OnRefreshFinished( |
| const PushMessagingAppIdentifier& app_identifier) override; |
| |
| // Sets a callback that can be used to listen for service worker |
| // subscription events. |
| void SetSubscribeFromWorkerCallback( |
| base::RepeatingCallback<void(/*registration id=*/int64_t)> callback); |
| |
| void SetMessageCallbackForTesting(const base::RepeatingClosure& callback); |
| void SetUnsubscribeCallbackForTesting(base::OnceClosure callback); |
| void SetInvalidationCallbackForTesting(base::OnceClosure callback); |
| void SetContentSettingChangedCallbackForTesting( |
| base::RepeatingClosure callback); |
| void SetServiceWorkerUnregisteredCallbackForTesting( |
| base::RepeatingClosure callback); |
| void SetServiceWorkerDatabaseWipedCallbackForTesting( |
| base::RepeatingClosure callback); |
| void SetRemoveExpiredSubscriptionsCallbackForTesting( |
| base::OnceClosure closure); |
| |
| private: |
| friend class PushMessagingBrowserTestBase; |
| friend class PushMessagingServiceTest; |
| friend class FCMRevocationTest; |
| FRIEND_TEST_ALL_PREFIXES(PushMessagingBrowserTest, PushEventOnShutdown); |
| FRIEND_TEST_ALL_PREFIXES(PushMessagingServiceTest, NormalizeSenderInfo); |
| FRIEND_TEST_ALL_PREFIXES(PushMessagingServiceTest, PayloadEncryptionTest); |
| FRIEND_TEST_ALL_PREFIXES(PushMessagingServiceTest, |
| TestMultipleIncomingPushMessages); |
| #if BUILDFLAG(IS_ANDROID) |
| FRIEND_TEST_ALL_PREFIXES(FCMRevocationTest, |
| TestPermissionRevocationClearPreferences); |
| FRIEND_TEST_ALL_PREFIXES(FCMRevocationTest, |
| TestPermissionRevocationNoPermissionFirstMessage); |
| FRIEND_TEST_ALL_PREFIXES(FCMRevocationTest, |
| TestPermissionRevocationGracePeriodIsOver); |
| #endif |
| |
| // A subscription is pending until it has succeeded or failed. |
| void IncreasePushSubscriptionCount(int add, bool is_pending); |
| void DecreasePushSubscriptionCount(int subtract, bool was_pending); |
| |
| // OnMessage methods --------------------------------------------------------- |
| |
| void DeliverMessageCallback(const std::string& app_id, |
| const GURL& requesting_origin, |
| int64_t service_worker_registration_id, |
| const gcm::IncomingMessage& message, |
| bool did_enqueue_message, |
| blink::mojom::PushEventStatus status); |
| |
| void DidHandleEnqueuedMessage( |
| const GURL& origin, |
| int64_t service_worker_registration_id, |
| base::OnceCallback<void(bool)> message_handled_callback, |
| bool did_show_generic_notification); |
| |
| void DidHandleMessage(const std::string& app_id, |
| const std::string& push_message_id, |
| bool did_show_generic_notification); |
| |
| void OnCheckedOrigin(PendingMessage message, |
| PermissionRevocationRequest::Outcome outcome); |
| |
| void DeliverNextQueuedMessageForServiceWorkerRegistration( |
| const GURL& origin, |
| int64_t service_worker_registration_id); |
| |
| void CheckOriginAndDispatchNextMessage(); |
| |
| // Subscribe methods --------------------------------------------------------- |
| |
| void DoSubscribe(PushMessagingAppIdentifier app_identifier, |
| blink::mojom::PushSubscriptionOptionsPtr options, |
| RegisterCallback callback, |
| int render_process_id, |
| int render_frame_id, |
| blink::mojom::PermissionStatus permission_status); |
| |
| void SubscribeEnd(RegisterCallback callback, |
| 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, |
| blink::mojom::PushRegistrationStatus status); |
| |
| void SubscribeEndWithError(RegisterCallback callback, |
| blink::mojom::PushRegistrationStatus status); |
| |
| void DidSubscribe(const PushMessagingAppIdentifier& app_identifier, |
| const std::string& sender_id, |
| RegisterCallback callback, |
| const std::string& subscription_id, |
| instance_id::InstanceID::Result result); |
| |
| void DidSubscribeWithEncryptionInfo( |
| const PushMessagingAppIdentifier& app_identifier, |
| RegisterCallback callback, |
| const std::string& subscription_id, |
| const GURL& endpoint, |
| std::string p256dh, |
| std::string auth_secret); |
| |
| // GetSubscriptionInfo methods ----------------------------------------------- |
| |
| void DidValidateSubscription(const std::string& app_id, |
| const std::string& sender_id, |
| const GURL& endpoint, |
| const std::optional<base::Time>& expiration_time, |
| SubscriptionInfoCallback callback, |
| bool is_valid); |
| |
| void DidGetEncryptionInfo(const GURL& endpoint, |
| const std::optional<base::Time>& expiration_time, |
| SubscriptionInfoCallback callback, |
| std::string p256dh, |
| std::string auth_secret) const; |
| |
| // Unsubscribe methods ------------------------------------------------------- |
| |
| // |origin|, |service_worker_registration_id| and |app_id| should be provided |
| // whenever they can be obtained. It's valid for |origin| to be empty and |
| // |service_worker_registration_id| to be kInvalidServiceWorkerRegistrationId, |
| // or for app_id to be empty, but not both at once. |
| void UnsubscribeInternal(blink::mojom::PushUnregistrationReason reason, |
| const GURL& origin, |
| int64_t service_worker_registration_id, |
| const std::string& app_id, |
| const std::string& sender_id, |
| UnregisterCallback callback); |
| |
| void DidClearPushSubscriptionId(blink::mojom::PushUnregistrationReason reason, |
| const std::string& app_id, |
| const std::string& sender_id, |
| UnregisterCallback callback); |
| |
| void DidUnregister(bool was_subscribed, gcm::GCMClient::Result result); |
| void DidDeleteID(const std::string& app_id, |
| bool was_subscribed, |
| instance_id::InstanceID::Result result); |
| void DidUnsubscribe(const std::string& app_id_when_instance_id, |
| bool was_subscribed); |
| |
| // OnContentSettingChanged methods ------------------------------------------- |
| |
| void GetPushSubscriptionFromAppIdentifier( |
| const PushMessagingAppIdentifier& app_identifier, |
| base::OnceCallback<void(blink::mojom::PushSubscriptionPtr)> callback); |
| |
| void DidGetSWData( |
| const PushMessagingAppIdentifier& app_identifier, |
| base::OnceCallback<void(blink::mojom::PushSubscriptionPtr)> callback, |
| const std::string& sender_id, |
| const std::string& subscription_id); |
| |
| void GetPushSubscriptionFromAppIdentifierEnd( |
| base::OnceCallback<void(blink::mojom::PushSubscriptionPtr)> callback, |
| const std::string& sender_id, |
| bool is_valid, |
| const GURL& endpoint, |
| const std::optional<base::Time>& expiration_time, |
| const std::vector<uint8_t>& p256dh, |
| const std::vector<uint8_t>& auth); |
| |
| // OnSubscriptionInvalidation methods----------------------------------------- |
| |
| void GetOldSubscription(PushMessagingAppIdentifier old_app_identifier, |
| const std::string& sender_id); |
| |
| // After gathering all relavent information to start the refresh, |
| // generate a new app id and initiate refresh |
| void StartRefresh(PushMessagingAppIdentifier old_app_identifier, |
| const std::string& sender_id, |
| blink::mojom::PushSubscriptionPtr old_subscription); |
| |
| // Makes a new susbcription and replaces the old subscription by new |
| // subscription in preferences and service worker database |
| void UpdateSubscription(PushMessagingAppIdentifier app_identifier, |
| blink::mojom::PushSubscriptionOptionsPtr options, |
| RegisterCallback callback); |
| |
| // After the subscription is updated, fire a `pushsubscriptionchange` event |
| // and notify the |refresher_| |
| void DidUpdateSubscription(const std::string& new_app_id, |
| const std::string& old_app_id, |
| blink::mojom::PushSubscriptionPtr old_subscription, |
| const std::string& sender_id, |
| 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); |
| |
| // SafeBrowsingDatabaseManager callbacks ------------------------------------- |
| |
| // Callback for Safe Browsing URL allowlist lookups. |
| void DidCheckHighConfidenceAllowlist( |
| const GURL& origin, |
| int64_t service_worker_registration_id, |
| const std::string& message_id, |
| std::optional<std::string> payload, |
| base::OnceCallback<void(blink::mojom::PushEventStatus)> callback, |
| bool allowlisted, |
| std::optional<safe_browsing::SafeBrowsingDatabaseManager:: |
| HighConfidenceAllowlistCheckLoggingDetails> |
| logging_details); |
| |
| // Helper methods ------------------------------------------------------------ |
| |
| // The subscription given in |identifier| will be unsubscribed (and a |
| // `pushsubscriptionchange` event fires if |
| // features::kPushSubscriptionChangeEvent is enabled) |
| void UnexpectedChange(PushMessagingAppIdentifier identifier, |
| blink::mojom::PushUnregistrationReason reason, |
| base::OnceClosure completed_closure); |
| |
| void UnexpectedUnsubscribe(const PushMessagingAppIdentifier& app_identifier, |
| blink::mojom::PushUnregistrationReason reason, |
| UnregisterCallback unregister_callback); |
| |
| void DidGetSenderIdUnexpectedUnsubscribe( |
| const PushMessagingAppIdentifier& app_identifier, |
| blink::mojom::PushUnregistrationReason reason, |
| UnregisterCallback callback, |
| const std::string& sender_id); |
| |
| // Checks if a given origin is allowed to use Push. |
| // |
| // `user_visible` is the userVisibleOnly value provided to the push |
| // registration. |
| // |
| // For most origins this checks if the origin has the notifications |
| // permission. An exception is for extensions that use service workers where |
| // `user_visible` is false. |
| bool IsPermissionSet(const GURL& origin, bool user_visible); |
| |
| // Wrapper around {GCMDriver, InstanceID}::GetEncryptionInfo. |
| void GetEncryptionInfoForAppId( |
| const std::string& app_id, |
| const std::string& sender_id, |
| gcm::GCMEncryptionProvider::EncryptionInfoCallback callback); |
| |
| gcm::GCMDriver* GetGCMDriver() const; |
| |
| instance_id::InstanceIDDriver* GetInstanceIDDriver() const; |
| |
| content::DevToolsBackgroundServicesContext* GetDevToolsContext( |
| const GURL& origin) const; |
| |
| // Testing methods ----------------------------------------------------------- |
| |
| using PushEventCallback = |
| base::OnceCallback<void(blink::mojom::PushEventStatus)>; |
| using MessageDispatchedCallback = |
| base::RepeatingCallback<void(const std::string& app_id, |
| const GURL& origin, |
| int64_t service_worker_registration_id, |
| std::optional<std::string> payload, |
| PushEventCallback callback)>; |
| |
| // Callback to be invoked when a message has been dispatched. Enables tests to |
| // observe message delivery instead of delivering it to the Service Worker. |
| void SetMessageDispatchedCallbackForTesting( |
| const MessageDispatchedCallback& callback) { |
| message_dispatched_callback_for_testing_ = callback; |
| } |
| |
| void OnAppTerminating(); |
| |
| raw_ptr<Profile> profile_; |
| std::unique_ptr<PermissionRevocationRequest> origin_revocation_request_; |
| std::queue<PendingMessage> messages_pending_permission_check_; |
| |
| // {Origin, ServiceWokerRegistratonId} key for message delivery queue. This |
| // ensures that we only deliver one message at a time per ServiceWorker. |
| using MessageDeliveryQueueKey = std::pair<GURL, int64_t>; |
| |
| // Queue of pending messages per ServiceWorkerRegstration to be delivered one |
| // at a time. This allows us to enforce visibility requirements. |
| base::flat_map<MessageDeliveryQueueKey, std::queue<PendingMessage>> |
| message_delivery_queue_; |
| |
| int push_subscription_count_; |
| int pending_push_subscription_count_; |
| |
| base::RepeatingClosure message_callback_for_testing_; |
| base::OnceClosure unsubscribe_callback_for_testing_; |
| base::RepeatingClosure content_setting_changed_callback_for_testing_; |
| base::RepeatingClosure service_worker_unregistered_callback_for_testing_; |
| base::RepeatingClosure service_worker_database_wiped_callback_for_testing_; |
| base::OnceClosure remove_expired_subscriptions_callback_for_testing_; |
| base::OnceClosure invalidation_callback_for_testing_; |
| |
| PushMessagingNotificationManager notification_manager_; |
| |
| PushMessagingRefresher refresher_; |
| |
| base::ScopedObservation<PushMessagingRefresher, |
| PushMessagingRefresher::Observer> |
| refresh_observation_{this}; |
| |
| MessageDispatchedCallback message_dispatched_callback_for_testing_; |
| |
| #if BUILDFLAG(ENABLE_BACKGROUND_MODE) |
| // KeepAlive registered while we have in-flight push messages, to make sure |
| // we can finish processing them without being interrupted by BrowserProcess |
| // teardown. |
| std::unique_ptr<ScopedKeepAlive> in_flight_keep_alive_; |
| |
| // Same as ScopedKeepAlive, but prevents |profile_| from getting deleted. |
| std::unique_ptr<ScopedProfileKeepAlive> in_flight_profile_keep_alive_; |
| #endif |
| |
| base::CallbackListSubscription on_app_terminating_subscription_; |
| |
| // Callback used to be alerted of a new service worker subscription. |
| std::optional<base::RepeatingCallback<void(int64_t)>> |
| subscribe_from_worker_callback_; |
| |
| // True when shutdown has started. Do not allow processing of incoming |
| // messages when this is true. |
| bool shutdown_started_ = false; |
| |
| int render_process_id_ = content::ChildProcessHost::kInvalidUniqueID; |
| |
| // Tracks those that are attempting to bypass the user visible |
| // requirement on push notifications. E.g. they set userVisibleOnly to false |
| // on push registration. |
| std::set<GURL> origins_requesting_user_visible_requirement_bypass; |
| |
| // Enables Safe Browsing URL allowlist lookups. May be a nullptr when ESB was |
| // not enabled at profile initialisation time. |
| scoped_refptr<safe_browsing::SafeBrowsingDatabaseManager> database_manager_; |
| |
| base::WeakPtrFactory<PushMessagingServiceImpl> weak_factory_{this}; |
| }; |
| |
| #endif // CHROME_BROWSER_PUSH_MESSAGING_PUSH_MESSAGING_SERVICE_IMPL_H_ |