summaryrefslogtreecommitdiff
path: root/chromium/chrome/browser/push_messaging/push_messaging_service_impl.h
blob: a99e6e578f48990ba4817d7328e4fe45c5c29fc1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
// Copyright 2014 The Chromium Authors. All rights reserved.
// 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 <queue>
#include <utility>
#include <vector>

#include "base/callback.h"
#include "base/callback_helpers.h"
#include "base/containers/flat_map.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/abusive_origin_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/content_settings/core/common/content_settings.h"
#include "components/content_settings/core/common/content_settings_types.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 "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_registrar.h"
#include "content/public/browser/push_messaging_service.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/blink/public/mojom/push_messaging/push_messaging.mojom-forward.h"

class GURL;
class Profile;
class PushMessagingAppIdentifier;
class PushMessagingServiceTest;
class ScopedKeepAlive;
class ScopedProfileKeepAlive;

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 content::NotificationObserver,
                                 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);

  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);

  // 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,
                           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 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 FirePushSubscriptionChange(
      const PushMessagingAppIdentifier& app_identifier,
      base::OnceClosure completed_closure,
      blink::mojom::PushSubscriptionPtr new_subscription,
      blink::mojom::PushSubscriptionPtr old_subscription);

  // KeyedService implementation.
  void Shutdown() override;

  // content::NotificationObserver implementation
  void Observe(int type,
               const content::NotificationSource& source,
               const content::NotificationDetails& details) 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;

  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_TEST_ALL_PREFIXES(PushMessagingServiceTest, NormalizeSenderInfo);
  FRIEND_TEST_ALL_PREFIXES(PushMessagingServiceTest, PayloadEncryptionTest);
  FRIEND_TEST_ALL_PREFIXES(PushMessagingServiceTest,
                           TestMultipleIncomingPushMessages);

  // 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 OnCheckedOriginForAbuse(
      PendingMessage message,
      AbusiveOriginPermissionRevocationRequest::Outcome outcome);

  void DeliverNextQueuedMessageForServiceWorkerRegistration(
      const GURL& origin,
      int64_t service_worker_registration_id);

  void CheckOriginForAbuseAndDispatchNextMessage();

  // Subscribe methods ---------------------------------------------------------

  void DoSubscribe(PushMessagingAppIdentifier app_identifier,
                   blink::mojom::PushSubscriptionOptionsPtr options,
                   RegisterCallback callback,
                   int render_process_id,
                   int render_frame_id,
                   ContentSetting permission_status);

  void SubscribeEnd(RegisterCallback callback,
                    const std::string& subscription_id,
                    const GURL& endpoint,
                    const absl::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 absl::optional<base::Time>& expiration_time,
      SubscriptionInfoCallback callback,
      bool is_valid);

  void DidGetEncryptionInfo(const GURL& endpoint,
                            const absl::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 absl::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 absl::optional<base::Time>& expiration_time,
                             const std::vector<uint8_t>& p256dh,
                             const std::vector<uint8_t>& auth,
                             blink::mojom::PushRegistrationStatus status);
  // 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);

  void FirePushSubscriptionChangeCallback(
      const PushMessagingAppIdentifier& app_identifier,
      blink::mojom::PushEventStatus status);

  // Checks if a given origin is allowed to use Push.
  bool IsPermissionSet(const GURL& origin, bool user_visible = true);

  // 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,
                                   absl::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;
  }

  raw_ptr<Profile> profile_;
  std::unique_ptr<AbusiveOriginPermissionRevocationRequest>
      abusive_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

  content::NotificationRegistrar registrar_;

  // True when shutdown has started. Do not allow processing of incoming
  // messages when this is true.
  bool shutdown_started_ = false;

  base::WeakPtrFactory<PushMessagingServiceImpl> weak_factory_{this};
};

#endif  // CHROME_BROWSER_PUSH_MESSAGING_PUSH_MESSAGING_SERVICE_IMPL_H_