Title: [293060] trunk
Revision
293060
Author
[email protected]
Date
2022-04-19 22:11:55 -0700 (Tue, 19 Apr 2022)

Log Message

Handle public token updates in webpushd
https://bugs.webkit.org/show_bug.cgi?id=239042

Reviewed by Brady Eidson.

Source/WebCore:

When the public token associated with our push service changes, we have to invalidate all
push subscriptions, since they all derive from the public token. This is only expected to
happen in very rare cases, e.g. keychain corruption.

To implement this, we now listen for public token updates from our push service, and pass
that on to PushDatabase. PushDatabase persists the token and deletes all subscriptions on
token change if necessary.

I had to change the schema to allow for this. openAndMigrateDatabaseImpl now migrates
existing data on schema change rather than nuking the whole database so users won't lose
their existing subscriptions.

* Modules/push-api/PushDatabase.cpp:
(WebCore::openAndMigrateDatabaseImpl):
(WebCore::PushDatabase::updatePublicToken):
(WebCore::PushDatabase::getPublicToken):

Source/WebKit:

When the public token associated with our push service changes, we have to invalidate all
push subscriptions, since they all derive from the public token. This is only expected to
happen in very rare cases, e.g. keychain corruption.

To implement this, we now listen for public token updates from our push service, and pass
that on to PushDatabase. PushDatabase persists the token and deletes all subscriptions on
token change if necessary.

* Shared/WebPushDaemonConstants.h:
(WebKit::WebPushD::messageTypeSendsReply):
* webpushd/ApplePushServiceConnection.mm:
(-[_WKAPSConnectionDelegate connection:didReceivePublicToken:]):
(-[_WKAPSConnectionDelegate connection:didReceiveIncomingMessage:]):
* webpushd/MockPushServiceConnection.mm:
(WebPushD::MockPushServiceConnection::MockPushServiceConnection):
(WebPushD::MockPushServiceConnection::setPublicTokenForTesting):
* webpushd/PushService.mm:
(WebPushD::PushService::PushService):
(WebPushD::PushService::setPublicTokenForTesting):
(WebPushD::PushService::didReceivePublicToken):
* webpushd/PushServiceConnection.mm:
(WebPushD::PushServiceConnection::startListeningForPublicToken):
(WebPushD::PushServiceConnection::didReceivePublicToken):
(WebPushD::PushServiceConnection::setPublicTokenForTesting):
* webpushd/WebPushDaemon.mm:
(WebPushD::Daemon::decodeAndHandleMessage):
(WebPushD::Daemon::setPublicTokenForTesting):

Tools:

 - Test that public token updates are handled properly at the PushDatabase and WebPushDaemon levels
 - Test schema migration in PushDatabase

* TestWebKitAPI/Tests/WebCore/PushDatabase.cpp:
(TestWebKitAPI::createDatabaseSync):
(TestWebKitAPI::getPublicTokenSync):
(TestWebKitAPI::updatePublicTokenSync):
(TestWebKitAPI::getRecordByBundleIdentifierAndScopeSync):
(TestWebKitAPI::getRowIdentifiersSync):
(TestWebKitAPI::PushDatabaseTest::getPublicToken):
(TestWebKitAPI::PushDatabaseTest::updatePublicToken):
(TestWebKitAPI::PushDatabaseTest::getRecordByBundleIdentifierAndScope):
(TestWebKitAPI::PushDatabaseTest::getRowIdentifiers):
(TestWebKitAPI::TEST_F):
(TestWebKitAPI::TEST):
(TestWebKitAPI::createDatabaseFromStatements):
* TestWebKitAPI/Tests/WebKitCocoa/WebPushDaemon.mm:

Modified Paths

Diff

Modified: trunk/Source/WebCore/ChangeLog (293059 => 293060)


--- trunk/Source/WebCore/ChangeLog	2022-04-20 03:05:47 UTC (rev 293059)
+++ trunk/Source/WebCore/ChangeLog	2022-04-20 05:11:55 UTC (rev 293060)
@@ -1,3 +1,27 @@
+2022-04-19  Ben Nham  <[email protected]>
+
+        Handle public token updates in webpushd
+        https://bugs.webkit.org/show_bug.cgi?id=239042
+
+        Reviewed by Brady Eidson.
+
+        When the public token associated with our push service changes, we have to invalidate all
+        push subscriptions, since they all derive from the public token. This is only expected to
+        happen in very rare cases, e.g. keychain corruption.
+
+        To implement this, we now listen for public token updates from our push service, and pass
+        that on to PushDatabase. PushDatabase persists the token and deletes all subscriptions on
+        token change if necessary.
+
+        I had to change the schema to allow for this. openAndMigrateDatabaseImpl now migrates
+        existing data on schema change rather than nuking the whole database so users won't lose
+        their existing subscriptions.
+
+        * Modules/push-api/PushDatabase.cpp:
+        (WebCore::openAndMigrateDatabaseImpl):
+        (WebCore::PushDatabase::updatePublicToken):
+        (WebCore::PushDatabase::getPublicToken):
+
 2022-04-19  Chris Dumez  <[email protected]>
 
         Use more r-value references for Text / CharacterData classes

Modified: trunk/Source/WebCore/Modules/push-api/PushDatabase.cpp (293059 => 293060)


--- trunk/Source/WebCore/Modules/push-api/PushDatabase.cpp	2022-04-20 03:05:47 UTC (rev 293059)
+++ trunk/Source/WebCore/Modules/push-api/PushDatabase.cpp	2022-04-20 05:11:55 UTC (rev 293060)
@@ -33,6 +33,7 @@
 #include "SQLiteFileSystem.h"
 #include "SQLiteTransaction.h"
 #include "SecurityOrigin.h"
+#include <iterator>
 #include <wtf/CrossThreadCopier.h>
 #include <wtf/Expected.h>
 #include <wtf/FileSystem.h>
@@ -39,6 +40,7 @@
 #include <wtf/RunLoop.h>
 #include <wtf/Scope.h>
 #include <wtf/UniqueRef.h>
+#include <wtf/text/StringConcatenateNumbers.h>
 
 #define PUSHDB_RELEASE_LOG(fmt, ...) RELEASE_LOG(Push, "%p - PushDatabase::" fmt, this, ##__VA_ARGS__)
 #define PUSHDB_RELEASE_LOG_ERROR(fmt, ...) RELEASE_LOG_ERROR(Push, "%p - PushDatabase::" fmt, this, ##__VA_ARGS__)
@@ -48,11 +50,11 @@
 
 namespace WebCore {
 
-static constexpr int currentPushDatabaseVersion = 2;
-#define kCurrentPushDatabaseVersionString "2"
+static constexpr ASCIILiteral pushDatabaseSchemaV1Statements[] = {
+    "PRAGMA auto_vacuum=INCREMENTAL"_s,
+};
 
-static const ASCIILiteral pushDatabaseSchemaV1Statements[] = {
-    "PRAGMA auto_vacuum=INCREMENTAL"_s,
+static constexpr ASCIILiteral pushDatabaseSchemaV2Statements[] = {
     "CREATE TABLE SubscriptionSets("
     "  rowID INTEGER PRIMARY KEY AUTOINCREMENT,"
     "  creationTime INT NOT NULL,"
@@ -74,9 +76,22 @@
     "  expirationTime INT,"
     "  UNIQUE(scope, subscriptionSetID))"_s,
     "CREATE INDEX Subscriptions_SubscriptionSetID_Index ON Subscriptions(subscriptionSetID)"_s,
-    "PRAGMA user_version = " kCurrentPushDatabaseVersionString ""_s
 };
 
+static constexpr ASCIILiteral pushDatabaseSchemaV3Statements[] = {
+    "CREATE TABLE Metadata(key TEXT, value, UNIQUE(key))"_s,
+};
+
+static constexpr Span<const ASCIILiteral> pushDatabaseSchemaStatements[] = {
+    { pushDatabaseSchemaV1Statements },
+    { pushDatabaseSchemaV2Statements },
+    { pushDatabaseSchemaV3Statements },
+};
+
+static constexpr int currentPushDatabaseVersion = std::size(pushDatabaseSchemaStatements);
+
+static constexpr ASCIILiteral publicTokenKey = "publicToken"_s;
+
 PushRecord PushRecord::isolatedCopy() const &
 {
     return {
@@ -150,23 +165,27 @@
         version = sql->columnInt(0);
     }
 
-    if (version && version != currentPushDatabaseVersion) {
-        // FIXME: add migration when we need it.
+    if (version < 0 || version > currentPushDatabaseVersion) {
         RELEASE_LOG_ERROR(Push, "Found unexpected PushDatabase version: %d (expected: %d) at path: %s", version, currentPushDatabaseVersion, path.utf8().data());
         return makeUnexpected(ShouldDeleteAndRetry::Yes);
     }
 
-    if (!version) {
+    if (version < currentPushDatabaseVersion) {
         SQLiteTransaction transaction(db);
         transaction.begin();
 
-        for (auto& statement : pushDatabaseSchemaV1Statements) {
-            if (!db->executeCommand(statement)) {
-                RELEASE_LOG_ERROR(Push, "Error executing PushDatabase DDL statement %s at path %s", statement.characters(), path.utf8().data());
-                return makeUnexpected(ShouldDeleteAndRetry::Yes);
+        for (auto i = version; i < currentPushDatabaseVersion; i++) {
+            for (auto statement : pushDatabaseSchemaStatements[i]) {
+                if (!db->executeCommand(statement)) {
+                    RELEASE_LOG_ERROR(Push, "Error executing PushDatabase DDL statement %s at path %s: %d", statement.characters(), path.utf8().data(), db->lastError());
+                    return makeUnexpected(ShouldDeleteAndRetry::Yes);
+                }
             }
         }
 
+        if (!db->executeCommandSlow(makeString("PRAGMA user_version = ", currentPushDatabaseVersion)))
+            RELEASE_LOG_ERROR(Push, "Error setting user version for PushDatabase at path %s: %d", path.utf8().data(), db->lastError());
+
         transaction.commit();
     }
 
@@ -280,11 +299,100 @@
 static void completeOnMainQueue(CompletionHandler<void(T)>&& completionHandler, U&& result)
 {
     ASSERT(!RunLoop::isMain());
-    WorkQueue::main().dispatch([completionHandler = WTFMove(completionHandler), result = crossThreadCopy(WTFMove(result))]() mutable {
+    WorkQueue::main().dispatch([completionHandler = WTFMove(completionHandler), result = crossThreadCopy(std::forward<U>(result))]() mutable {
         completionHandler(WTFMove(result));
     });
 }
 
+void PushDatabase::updatePublicToken(Span<const uint8_t> publicToken, CompletionHandler<void(PublicTokenChanged)>&& completionHandler)
+{
+    dispatchOnWorkQueue([this, newPublicToken = Vector<uint8_t> { publicToken }, completionHandler = WTFMove(completionHandler)]() mutable {
+        SQLiteTransaction transaction(m_db);
+        transaction.begin();
+
+        auto result = PublicTokenChanged::No;
+        Vector<uint8_t> currentPublicToken;
+        auto scope = makeScopeExit([&completionHandler, &result] {
+            completeOnMainQueue(WTFMove(completionHandler), result);
+        });
+
+        {
+            auto sql = cachedStatementOnQueue("SELECT value FROM Metadata WHERE key = ?"_s);
+            if (!sql || sql->bindText(1, publicTokenKey) != SQLITE_OK) {
+                PUSHDB_RELEASE_LOG_BIND_ERROR();
+                return;
+            }
+
+            if (sql->step() == SQLITE_ROW)
+                currentPublicToken = sql->columnBlob(0);
+        }
+
+        if (currentPublicToken == newPublicToken)
+            return;
+
+        {
+            auto sql = cachedStatementOnQueue("REPLACE INTO Metadata(key, value) VALUES(?, ?)"_s);
+            if (!sql
+                || sql->bindText(1, publicTokenKey) != SQLITE_OK
+                || sql->bindBlob(2, newPublicToken) != SQLITE_OK) {
+                PUSHDB_RELEASE_LOG_BIND_ERROR();
+                return;
+            }
+
+            if (sql->step() != SQLITE_DONE) {
+                RELEASE_LOG_ERROR(Push, "Failed to save new public token: %d", m_db->lastError());
+                return;
+            }
+        }
+
+        // If we are updating an old version of the database where currentPublicToken doesn't exist, just
+        // save the initial publicToken without deleting all subscriptions and notifying the caller that
+        // the token changed.
+        if (!currentPublicToken.isEmpty()) {
+            auto deleteSubscriptionSets = cachedStatementOnQueue("DELETE FROM SubscriptionSets"_s);
+            auto deleteSubscriptions = cachedStatementOnQueue("DELETE FROM Subscriptions"_s);
+
+            if (!deleteSubscriptionSets || !deleteSubscriptions) {
+                PUSHDB_RELEASE_LOG_BIND_ERROR();
+                return;
+            }
+
+            if (deleteSubscriptionSets->step() != SQLITE_DONE || deleteSubscriptions->step() != SQLITE_DONE) {
+                RELEASE_LOG_ERROR(Push, "Failed to delete subscriptions: %d", m_db->lastError());
+                return;
+            }
+
+            result = PublicTokenChanged::Yes;
+        }
+
+        scope.release();
+        transaction.commit();
+        completeOnMainQueue(WTFMove(completionHandler), result);
+    });
+}
+
+void PushDatabase::getPublicToken(CompletionHandler<void(Vector<uint8_t>&&)>&& completionHandler)
+{
+    dispatchOnWorkQueue([this, completionHandler = WTFMove(completionHandler)]() mutable {
+        SQLiteTransaction transaction(m_db);
+        transaction.begin();
+
+        auto sql = cachedStatementOnQueue("SELECT value FROM Metadata WHERE key = ?"_s);
+        if (!sql || sql->bindText(1, publicTokenKey) != SQLITE_OK) {
+            PUSHDB_RELEASE_LOG_BIND_ERROR();
+            completeOnMainQueue(WTFMove(completionHandler), Vector<uint8_t> { });
+            return;
+        }
+
+        Vector<uint8_t> result;
+        if (sql->step() == SQLITE_ROW)
+            result = sql->columnBlob(0);
+
+        transaction.commit();
+        completeOnMainQueue(WTFMove(completionHandler), WTFMove(result));
+    });
+}
+
 void PushDatabase::insertRecord(const PushRecord& record, CompletionHandler<void(std::optional<PushRecord>&&)>&& completionHandler)
 {
     dispatchOnWorkQueue([this, record = crossThreadCopy(record), completionHandler = WTFMove(completionHandler)]() mutable {

Modified: trunk/Source/WebCore/Modules/push-api/PushDatabase.h (293059 => 293060)


--- trunk/Source/WebCore/Modules/push-api/PushDatabase.h	2022-04-20 03:05:47 UTC (rev 293059)
+++ trunk/Source/WebCore/Modules/push-api/PushDatabase.h	2022-04-20 05:11:55 UTC (rev 293060)
@@ -36,6 +36,7 @@
 #include <wtf/FastMalloc.h>
 #include <wtf/HashMap.h>
 #include <wtf/HashSet.h>
+#include <wtf/Span.h>
 #include <wtf/UniqueRef.h>
 #include <wtf/Vector.h>
 #include <wtf/WorkQueue.h>
@@ -76,6 +77,10 @@
     WEBCORE_EXPORT static void create(const String& path, CreationHandler&&);
     WEBCORE_EXPORT ~PushDatabase();
 
+    enum class PublicTokenChanged : bool { No, Yes };
+    WEBCORE_EXPORT void updatePublicToken(Span<const uint8_t>, CompletionHandler<void(PublicTokenChanged)>&&);
+    WEBCORE_EXPORT void getPublicToken(CompletionHandler<void(Vector<uint8_t>&&)>&&);
+
     WEBCORE_EXPORT void insertRecord(const PushRecord&, CompletionHandler<void(std::optional<PushRecord>&&)>&&);
     WEBCORE_EXPORT void removeRecordByIdentifier(PushSubscriptionIdentifier, CompletionHandler<void(bool)>&&);
     WEBCORE_EXPORT void getRecordByTopic(const String& topic, CompletionHandler<void(std::optional<PushRecord>&&)>&&);

Modified: trunk/Source/WebKit/ChangeLog (293059 => 293060)


--- trunk/Source/WebKit/ChangeLog	2022-04-20 03:05:47 UTC (rev 293059)
+++ trunk/Source/WebKit/ChangeLog	2022-04-20 05:11:55 UTC (rev 293060)
@@ -1,3 +1,38 @@
+2022-04-19  Ben Nham  <[email protected]>
+
+        Handle public token updates in webpushd
+        https://bugs.webkit.org/show_bug.cgi?id=239042
+
+        Reviewed by Brady Eidson.
+
+        When the public token associated with our push service changes, we have to invalidate all
+        push subscriptions, since they all derive from the public token. This is only expected to
+        happen in very rare cases, e.g. keychain corruption.
+
+        To implement this, we now listen for public token updates from our push service, and pass
+        that on to PushDatabase. PushDatabase persists the token and deletes all subscriptions on
+        token change if necessary.
+
+        * Shared/WebPushDaemonConstants.h:
+        (WebKit::WebPushD::messageTypeSendsReply):
+        * webpushd/ApplePushServiceConnection.mm:
+        (-[_WKAPSConnectionDelegate connection:didReceivePublicToken:]):
+        (-[_WKAPSConnectionDelegate connection:didReceiveIncomingMessage:]):
+        * webpushd/MockPushServiceConnection.mm:
+        (WebPushD::MockPushServiceConnection::MockPushServiceConnection):
+        (WebPushD::MockPushServiceConnection::setPublicTokenForTesting):
+        * webpushd/PushService.mm:
+        (WebPushD::PushService::PushService):
+        (WebPushD::PushService::setPublicTokenForTesting):
+        (WebPushD::PushService::didReceivePublicToken):
+        * webpushd/PushServiceConnection.mm:
+        (WebPushD::PushServiceConnection::startListeningForPublicToken):
+        (WebPushD::PushServiceConnection::didReceivePublicToken):
+        (WebPushD::PushServiceConnection::setPublicTokenForTesting):
+        * webpushd/WebPushDaemon.mm:
+        (WebPushD::Daemon::decodeAndHandleMessage):
+        (WebPushD::Daemon::setPublicTokenForTesting):
+
 2022-04-19  Wenson Hsieh  <[email protected]>
 
         [iOS] Dictation text that contains emojis is inserted twice upon finalization

Modified: trunk/Source/WebKit/Shared/WebPushDaemonConstants.h (293059 => 293060)


--- trunk/Source/WebKit/Shared/WebPushDaemonConstants.h	2022-04-20 03:05:47 UTC (rev 293059)
+++ trunk/Source/WebKit/Shared/WebPushDaemonConstants.h	2022-04-20 05:11:55 UTC (rev 293060)
@@ -55,6 +55,7 @@
     IncrementSilentPushCount,
     RemoveAllPushSubscriptions,
     RemovePushSubscriptionsForOrigin,
+    SetPublicTokenForTesting,
 };
 
 inline bool messageTypeSendsReply(MessageType messageType)
@@ -74,6 +75,7 @@
     case MessageType::IncrementSilentPushCount:
     case MessageType::RemoveAllPushSubscriptions:
     case MessageType::RemovePushSubscriptionsForOrigin:
+    case MessageType::SetPublicTokenForTesting:
         return true;
     case MessageType::SetDebugModeIsEnabled:
     case MessageType::UpdateConnectionConfiguration:

Modified: trunk/Source/WebKit/webpushd/ApplePushServiceConnection.mm (293059 => 293060)


--- trunk/Source/WebKit/webpushd/ApplePushServiceConnection.mm	2022-04-20 03:05:47 UTC (rev 293059)
+++ trunk/Source/WebKit/webpushd/ApplePushServiceConnection.mm	2022-04-20 05:11:55 UTC (rev 293060)
@@ -48,13 +48,15 @@
 - (void)connection:(APSConnection *)connection didReceivePublicToken:(NSData *)publicToken
 {
     UNUSED_PARAM(connection);
-    UNUSED_PARAM(publicToken);
+    ASSERT(isMainRunLoop());
+
+    if (_connection && publicToken.length)
+        _connection->didReceivePublicToken(Vector<uint8_t> { static_cast<const uint8_t*>(publicToken.bytes), publicToken.length });
 }
 
 - (void)connection:(APSConnection *)connection didReceiveIncomingMessage:(APSIncomingMessage *)message
 {
     UNUSED_PARAM(connection);
-
     ASSERT(isMainRunLoop());
 
     if (_connection)

Modified: trunk/Source/WebKit/webpushd/MockPushServiceConnection.h (293059 => 293060)


--- trunk/Source/WebKit/webpushd/MockPushServiceConnection.h	2022-04-20 03:05:47 UTC (rev 293059)
+++ trunk/Source/WebKit/webpushd/MockPushServiceConnection.h	2022-04-20 05:11:55 UTC (rev 293060)
@@ -50,6 +50,8 @@
     void setNonWakingTopics(Vector<String>&&) override;
 
     void setTopicLists(TopicLists&&) override;
+
+    void setPublicTokenForTesting(Vector<uint8_t>&&) override;
 };
 
 } // namespace WebPushD

Modified: trunk/Source/WebKit/webpushd/MockPushServiceConnection.mm (293059 => 293060)


--- trunk/Source/WebKit/webpushd/MockPushServiceConnection.mm	2022-04-20 03:05:47 UTC (rev 293059)
+++ trunk/Source/WebKit/webpushd/MockPushServiceConnection.mm	2022-04-20 05:11:55 UTC (rev 293060)
@@ -34,6 +34,7 @@
 
 MockPushServiceConnection::MockPushServiceConnection()
 {
+    didReceivePublicToken(Vector<uint8_t> { 'a', 'b', 'c' });
 }
 
 MockPushServiceConnection::~MockPushServiceConnection() = default;
@@ -104,4 +105,9 @@
 {
 }
 
+void MockPushServiceConnection::setPublicTokenForTesting(Vector<uint8_t>&& token)
+{
+    didReceivePublicToken(WTFMove(token));
+}
+
 } // namespace WebPushD

Modified: trunk/Source/WebKit/webpushd/PushService.h (293059 => 293060)


--- trunk/Source/WebKit/webpushd/PushService.h	2022-04-20 03:05:47 UTC (rev 293059)
+++ trunk/Source/WebKit/webpushd/PushService.h	2022-04-20 05:11:55 UTC (rev 293060)
@@ -70,6 +70,8 @@
     void didCompleteSubscribeRequest(SubscribeRequest&);
     void didCompleteUnsubscribeRequest(UnsubscribeRequest&);
 
+    void setPublicTokenForTesting(Vector<uint8_t>&&);
+    void didReceivePublicToken(Vector<uint8_t>&&);
     void didReceivePushMessage(NSString *topic, NSDictionary *userInfo, CompletionHandler<void()>&& = [] { });
 
 private:

Modified: trunk/Source/WebKit/webpushd/PushService.mm (293059 => 293060)


--- trunk/Source/WebKit/webpushd/PushService.mm	2022-04-20 03:05:47 UTC (rev 293059)
+++ trunk/Source/WebKit/webpushd/PushService.mm	2022-04-20 05:11:55 UTC (rev 293060)
@@ -116,6 +116,10 @@
 {
     RELEASE_ASSERT(m_incomingPushMessageHandler);
 
+    m_connection->startListeningForPublicToken([this](auto&& token) {
+        didReceivePublicToken(WTFMove(token));
+    });
+
     m_connection->startListeningForPushMessages([this](NSString *topic, NSDictionary *userInfo) {
         didReceivePushMessage(topic, userInfo);
     });
@@ -621,6 +625,24 @@
     return message;
 }
 
+void PushService::setPublicTokenForTesting(Vector<uint8_t>&& token)
+{
+    m_connection->setPublicTokenForTesting(WTFMove(token));
+}
+
+void PushService::didReceivePublicToken(Vector<uint8_t>&& token)
+{
+    m_database->updatePublicToken(token, [this](auto result) mutable {
+        if (result == PushDatabase::PublicTokenChanged::No) {
+            RELEASE_LOG(Push, "Received expected public token");
+            return;
+        }
+
+        RELEASE_LOG_ERROR(Push, "Public token changed; invalidated all existing push subscriptions");
+        updateTopicLists(m_connection, m_database, []() { });
+    });
+}
+
 void PushService::didReceivePushMessage(NSString* topic, NSDictionary* userInfo, CompletionHandler<void()>&& completionHandler)
 {
     auto messageResult = makeRawPushMessage(topic, userInfo);

Modified: trunk/Source/WebKit/webpushd/PushServiceConnection.h (293059 => 293060)


--- trunk/Source/WebKit/webpushd/PushServiceConnection.h	2022-04-20 03:05:47 UTC (rev 293059)
+++ trunk/Source/WebKit/webpushd/PushServiceConnection.h	2022-04-20 05:11:55 UTC (rev 293060)
@@ -74,10 +74,16 @@
     };
     virtual void setTopicLists(TopicLists&&) = 0;
 
+    void startListeningForPublicToken(Function<void(Vector<uint8_t>&&)>&&);
+    void didReceivePublicToken(Vector<uint8_t>&&);
+    virtual void setPublicTokenForTesting(Vector<uint8_t>&&);
+
     void startListeningForPushMessages(IncomingPushMessageHandler&&);
     void didReceivePushMessage(NSString *topic, NSDictionary *userInfo);
 
 private:
+    Function<void(Vector<uint8_t>&&)> m_publicTokenChangeHandler;
+    Vector<uint8_t> m_pendingPublicToken;
     IncomingPushMessageHandler m_incomingPushMessageHandler;
     Deque<std::pair<RetainPtr<NSString>, RetainPtr<NSDictionary>>> m_pendingPushes;
 };

Modified: trunk/Source/WebKit/webpushd/PushServiceConnection.mm (293059 => 293060)


--- trunk/Source/WebKit/webpushd/PushServiceConnection.mm	2022-04-20 03:05:47 UTC (rev 293059)
+++ trunk/Source/WebKit/webpushd/PushServiceConnection.mm	2022-04-20 05:11:55 UTC (rev 293060)
@@ -37,6 +37,30 @@
     return PushCrypto::ClientKeys::generate();
 }
 
+void PushServiceConnection::startListeningForPublicToken(Function<void(Vector<uint8_t>&&)>&& handler)
+{
+    m_publicTokenChangeHandler = WTFMove(handler);
+
+    if (m_pendingPublicToken.isEmpty())
+        return;
+
+    m_publicTokenChangeHandler(std::exchange(m_pendingPublicToken, { }));
+}
+
+void PushServiceConnection::didReceivePublicToken(Vector<uint8_t>&& token)
+{
+    if (!m_publicTokenChangeHandler) {
+        m_pendingPublicToken = WTFMove(token);
+        return;
+    }
+
+    m_publicTokenChangeHandler(WTFMove(token));
+}
+
+void PushServiceConnection::setPublicTokenForTesting(Vector<uint8_t>&&)
+{
+}
+
 void PushServiceConnection::startListeningForPushMessages(IncomingPushMessageHandler&& handler)
 {
     m_incomingPushMessageHandler = WTFMove(handler);

Modified: trunk/Source/WebKit/webpushd/WebPushDaemon.h (293059 => 293060)


--- trunk/Source/WebKit/webpushd/WebPushDaemon.h	2022-04-20 03:05:47 UTC (rev 293059)
+++ trunk/Source/WebKit/webpushd/WebPushDaemon.h	2022-04-20 05:11:55 UTC (rev 293060)
@@ -87,6 +87,7 @@
     void incrementSilentPushCount(ClientConnection*, const WebCore::SecurityOriginData&, CompletionHandler<void(unsigned)>&&);
     void removeAllPushSubscriptions(ClientConnection*, CompletionHandler<void(unsigned)>&&);
     void removePushSubscriptionsForOrigin(ClientConnection*, const WebCore::SecurityOriginData&, CompletionHandler<void(unsigned)>&&);
+    void setPublicTokenForTesting(ClientConnection*, const String& publicToken, CompletionHandler<void()>&&);
 
     void broadcastDebugMessage(StringView);
     void broadcastAllConnectionIdentities();

Modified: trunk/Source/WebKit/webpushd/WebPushDaemon.mm (293059 => 293060)


--- trunk/Source/WebKit/webpushd/WebPushDaemon.mm	2022-04-20 03:05:47 UTC (rev 293059)
+++ trunk/Source/WebKit/webpushd/WebPushDaemon.mm	2022-04-20 05:11:55 UTC (rev 293060)
@@ -134,11 +134,21 @@
 REPLY(unsigned)
 END
 
+FUNCTION(setPublicTokenForTesting)
+ARGUMENTS(String)
+REPLY()
+END
+
+
 #undef FUNCTION
 #undef ARGUMENTS
 #undef REPLY
 #undef END
 
+#define EMPTY_REPLY(mf) WebPushD::EncodedMessage mf::encodeReply() { return { }; }
+EMPTY_REPLY(setPublicTokenForTesting);
+#undef EMPTY_REPLY
+
 WebPushD::EncodedMessage echoTwice::encodeReply(String reply)
 {
     WebKit::Daemon::Encoder encoder;
@@ -445,6 +455,9 @@
     case MessageType::RemovePushSubscriptionsForOrigin:
         handleWebPushDMessageWithReply<MessageInfo::removePushSubscriptionsForOrigin>(clientConnection, encodedMessage, WTFMove(replySender));
         break;
+    case MessageType::SetPublicTokenForTesting:
+        handleWebPushDMessageWithReply<MessageInfo::setPublicTokenForTesting>(clientConnection, encodedMessage, WTFMove(replySender));
+        break;
     }
 }
 
@@ -722,6 +735,19 @@
     });
 }
 
+void Daemon::setPublicTokenForTesting(ClientConnection*, const String& publicToken, CompletionHandler<void()>&& replySender)
+{
+    runAfterStartingPushService([this, publicToken, replySender = WTFMove(replySender)]() mutable {
+        if (!m_pushService) {
+            replySender();
+            return;
+        }
+
+        m_pushService->setPublicTokenForTesting(Vector<uint8_t> { publicToken.utf8().bytes() });
+        replySender();
+    });
+}
+
 ClientConnection* Daemon::toClientConnection(xpc_connection_t connection)
 {
     auto clientConnection = m_connectionMap.get(connection);

Modified: trunk/Tools/ChangeLog (293059 => 293060)


--- trunk/Tools/ChangeLog	2022-04-20 03:05:47 UTC (rev 293059)
+++ trunk/Tools/ChangeLog	2022-04-20 05:11:55 UTC (rev 293060)
@@ -1,3 +1,28 @@
+2022-04-19  Ben Nham  <[email protected]>
+
+        Handle public token updates in webpushd
+        https://bugs.webkit.org/show_bug.cgi?id=239042
+
+        Reviewed by Brady Eidson.
+
+         - Test that public token updates are handled properly at the PushDatabase and WebPushDaemon levels
+         - Test schema migration in PushDatabase
+
+        * TestWebKitAPI/Tests/WebCore/PushDatabase.cpp:
+        (TestWebKitAPI::createDatabaseSync):
+        (TestWebKitAPI::getPublicTokenSync):
+        (TestWebKitAPI::updatePublicTokenSync):
+        (TestWebKitAPI::getRecordByBundleIdentifierAndScopeSync):
+        (TestWebKitAPI::getRowIdentifiersSync):
+        (TestWebKitAPI::PushDatabaseTest::getPublicToken):
+        (TestWebKitAPI::PushDatabaseTest::updatePublicToken):
+        (TestWebKitAPI::PushDatabaseTest::getRecordByBundleIdentifierAndScope):
+        (TestWebKitAPI::PushDatabaseTest::getRowIdentifiers):
+        (TestWebKitAPI::TEST_F):
+        (TestWebKitAPI::TEST):
+        (TestWebKitAPI::createDatabaseFromStatements):
+        * TestWebKitAPI/Tests/WebKitCocoa/WebPushDaemon.mm:
+
 2022-04-19  Chris Dumez  <[email protected]>
 
         Replace String::replaceWithLiteral() with a String::replace() overload that takes in an ASCIILiteral

Modified: trunk/Tools/TestWebKitAPI/Tests/WebCore/PushDatabase.cpp (293059 => 293060)


--- trunk/Tools/TestWebKitAPI/Tests/WebCore/PushDatabase.cpp	2022-04-20 03:05:47 UTC (rev 293059)
+++ trunk/Tools/TestWebKitAPI/Tests/WebCore/PushDatabase.cpp	2022-04-20 05:11:55 UTC (rev 293060)
@@ -27,6 +27,7 @@
 #include "Utilities.h"
 #include <WebCore/PushDatabase.h>
 #include <WebCore/SQLiteDatabase.h>
+#include <iterator>
 #include <wtf/FileSystem.h>
 
 #if ENABLE(SERVICE_WORKER)
@@ -43,6 +44,77 @@
     return path;
 }
 
+static std::unique_ptr<PushDatabase> createDatabaseSync(const String& path)
+{
+    std::unique_ptr<PushDatabase> result;
+    bool done = false;
+
+    PushDatabase::create(path, [&done, &result](std::unique_ptr<PushDatabase>&& database) {
+        result = WTFMove(database);
+        done = true;
+    });
+    Util::run(&done);
+
+    return result;
+}
+
+static Vector<uint8_t> getPublicTokenSync(PushDatabase& database)
+{
+    bool done = false;
+    Vector<uint8_t> getResult;
+
+    database.getPublicToken([&done, &getResult](auto&& result) {
+        getResult = WTFMove(result);
+        done = true;
+    });
+    Util::run(&done);
+
+    return getResult;
+}
+
+static PushDatabase::PublicTokenChanged updatePublicTokenSync(PushDatabase& database, Span<const uint8_t> token)
+{
+    bool done = false;
+    auto updateResult = PushDatabase::PublicTokenChanged::No;
+
+    database.updatePublicToken(token, [&done, &updateResult](auto&& result) {
+        updateResult = WTFMove(result);
+        done = true;
+    });
+    Util::run(&done);
+
+    return updateResult;
+}
+
+static std::optional<PushRecord> getRecordByBundleIdentifierAndScopeSync(PushDatabase& database, const String& bundleID, const String& scope)
+{
+    bool done = false;
+    std::optional<PushRecord> getResult;
+
+    database.getRecordByBundleIdentifierAndScope(bundleID, scope, [&done, &getResult](std::optional<PushRecord>&& result) {
+        getResult = WTFMove(result);
+        done = true;
+    });
+    Util::run(&done);
+
+    return getResult;
+}
+
+static HashSet<uint64_t> getRowIdentifiersSync(PushDatabase& database)
+{
+    bool done = false;
+    HashSet<uint64_t> rowIdentifiers;
+
+    database.getIdentifiers([&done, &rowIdentifiers](HashSet<PushSubscriptionIdentifier>&& identifiers) {
+        for (auto identifier : identifiers)
+            rowIdentifiers.add(identifier.toUInt64());
+        done = true;
+    });
+    Util::run(&done);
+
+    return rowIdentifiers;
+}
+
 static Vector<String> getTopicsSync(PushDatabase& database)
 {
     bool done = false;
@@ -66,7 +138,7 @@
         "com.apple.webapp"_s,
         "https://www.apple.com"_s,
         "https://www.apple.com/iphone"_s,
-        "https://pushEndpoint2"_s,
+        "https://pushEndpoint1"_s,
         "topic1"_s,
         { 5, 6 },
         { 6, 7 },
@@ -78,8 +150,8 @@
         "com.apple.Safari"_s,
         "https://www.webkit.org"_s,
         "https://www.webkit.org/blog"_s,
-        "https://pushEndpoint4"_s,
-        "topic4"_s,
+        "https://pushEndpoint2"_s,
+        "topic2"_s,
         { 14, 15 },
         { 16, 17 },
         { 18, 19 },
@@ -90,8 +162,8 @@
         "com.apple.Safari"_s,
         "https://www.apple.com"_s,
         "https://www.apple.com/mac"_s,
-        "https://pushEndpoint1"_s,
-        "topic2"_s,
+        "https://pushEndpoint3"_s,
+        "topic3"_s,
         { 0, 1 },
         { 1, 2 },
         { 2, 3 },
@@ -103,8 +175,8 @@
         "com.apple.Safari"_s,
         "https://www.apple.com"_s,
         "https://www.apple.com/iphone"_s,
-        "https://pushEndpoint3"_s,
-        "topic3"_s,
+        "https://pushEndpoint4"_s,
+        "topic4"_s,
         { 9, 10 },
         { 10, 11 },
         { 11, 12 },
@@ -116,6 +188,16 @@
     std::optional<PushRecord> insertResult3;
     std::optional<PushRecord> insertResult4;
 
+    Vector<uint8_t> getPublicToken()
+    {
+        return getPublicTokenSync(*db);
+    }
+
+    PushDatabase::PublicTokenChanged updatePublicToken(Span<const uint8_t> token)
+    {
+        return updatePublicTokenSync(*db, token);
+    }
+
     std::optional<PushRecord> insertRecord(const PushRecord& record)
     {
         bool done = false;
@@ -160,31 +242,12 @@
 
     std::optional<PushRecord> getRecordByBundleIdentifierAndScope(const String& bundleID, const String& scope)
     {
-        bool done = false;
-        std::optional<PushRecord> getResult;
-
-        db->getRecordByBundleIdentifierAndScope(bundleID, scope, [&done, &getResult](std::optional<PushRecord>&& result) {
-            getResult = WTFMove(result);
-            done = true;
-        });
-        Util::run(&done);
-
-        return getResult;
+        return getRecordByBundleIdentifierAndScopeSync(*db, bundleID, scope);
     }
 
     HashSet<uint64_t> getRowIdentifiers()
     {
-        bool done = false;
-        HashSet<uint64_t> rowIdentifiers;
-
-        db->getIdentifiers([&done, &rowIdentifiers](HashSet<PushSubscriptionIdentifier>&& identifiers) {
-            for (auto identifier : identifiers)
-                rowIdentifiers.add(identifier.toUInt64());
-            done = true;
-        });
-        Util::run(&done);
-
-        return rowIdentifiers;
+        return getRowIdentifiersSync(*db);
     }
 
     Vector<String> getTopics()
@@ -236,13 +299,7 @@
 
     void SetUp() final
     {
-        bool done = false;
-        PushDatabase::create(makeTemporaryDatabasePath(), [this, &done](std::unique_ptr<PushDatabase>&& database) {
-            db = WTFMove(database);
-            done = true;
-        });
-        Util::run(&done);
-
+        db = createDatabaseSync(SQLiteDatabase::inMemoryPath());
         ASSERT_TRUE(db);
         ASSERT_TRUE(insertResult1 = insertRecord(record1));
         ASSERT_TRUE(insertResult2 = insertRecord(record2));
@@ -266,6 +323,36 @@
         && a.expirationTime == b.expirationTime;
 }
 
+TEST_F(PushDatabaseTest, UpdatePublicToken)
+{
+    Vector<uint8_t> initialToken = { 'a', 'b', 'c' };
+    Vector<uint8_t> modifiedToken = { 'd', 'e', 'f' };
+
+    // Setting the initial public token shouldn't delete anything.
+    auto updateResult1 = updatePublicToken(initialToken);
+    EXPECT_EQ(updateResult1, PushDatabase::PublicTokenChanged::No);
+    EXPECT_EQ(getRowIdentifiers(), (HashSet<uint64_t> { 1, 2, 3, 4 }));
+
+    auto getResult1 = getPublicToken();
+    EXPECT_EQ(getResult1, initialToken);
+
+    // Setting the same token again should do nothing.
+    auto updateResult2 = updatePublicToken(initialToken);
+    EXPECT_EQ(updateResult2, PushDatabase::PublicTokenChanged::No);
+    EXPECT_EQ(getRowIdentifiers(), (HashSet<uint64_t> { 1, 2, 3, 4 }));
+
+    auto getResult2 = getPublicToken();
+    EXPECT_EQ(getResult2, initialToken);
+
+    // Changing the public token afterwards should delete everything.
+    auto updateResult3 = updatePublicToken(modifiedToken);
+    EXPECT_EQ(updateResult3, PushDatabase::PublicTokenChanged::Yes);
+    EXPECT_TRUE(getRowIdentifiers().isEmpty());
+
+    auto getResult3 = getPublicToken();
+    EXPECT_EQ(getResult3, modifiedToken);
+}
+
 TEST_F(PushDatabaseTest, InsertRecord)
 {
     auto expectedRecord1 = record1;
@@ -403,13 +490,7 @@
     constexpr unsigned recordCount = 256;
 
     {
-        std::unique_ptr<PushDatabase> createResult;
-        bool done = false;
-        PushDatabase::create(path, [&createResult, &done](std::unique_ptr<PushDatabase>&& database) {
-            createResult = WTFMove(database);
-            done = true;
-        });
-        Util::run(&done);
+        auto createResult = createDatabaseSync(path);
         ASSERT_TRUE(createResult);
 
         auto& database = *createResult;
@@ -442,13 +523,7 @@
     }
 
     {
-        std::unique_ptr<PushDatabase> createResult;
-        bool done = false;
-        PushDatabase::create(path, [&createResult, &done](std::unique_ptr<PushDatabase>&& database) {
-            createResult = WTFMove(database);
-            done = true;
-        });
-        Util::run(&done);
+        auto createResult = createDatabaseSync(path);
         ASSERT_TRUE(createResult);
 
         auto topics = getTopicsSync(*createResult);
@@ -467,13 +542,7 @@
     }
 
     {
-        std::unique_ptr<PushDatabase> createResult;
-        bool done = false;
-        PushDatabase::create(path, [&createResult, &done](std::unique_ptr<PushDatabase>&& database) {
-            createResult = WTFMove(database);
-            done = true;
-        });
-        Util::run(&done);
+        auto createResult = createDatabaseSync(path);
         ASSERT_TRUE(createResult);
     }
 
@@ -491,6 +560,74 @@
     }
 }
 
+static bool createDatabaseFromStatements(String path, ASCIILiteral* statements, size_t count)
+{
+    SQLiteDatabase db;
+    db.open(path);
+
+    for (size_t i = 0; i < count; i++) {
+        if (!db.executeCommand(statements[i]))
+            return false;
+    }
+
+    return true;
+}
+
+// Acquired by running .dump from the sqlite3 shell on a V2 database.
+static ASCIILiteral pushDatabaseV2Statements[] = {
+    "CREATE TABLE SubscriptionSets(  rowID INTEGER PRIMARY KEY AUTOINCREMENT,  creationTime INT NOT NULL,  bundleID TEXT NOT NULL,  securityOrigin TEXT NOT NULL,  silentPushCount INT NOT NULL,  UNIQUE(bundleID, securityOrigin))"_s,
+    "INSERT INTO SubscriptionSets VALUES(1,1649541001,'com.apple.webapp','https://www.apple.com',0)"_s,
+    "INSERT INTO SubscriptionSets VALUES(2,1649541001,'com.apple.Safari','https://www.webkit.org',0)"_s,
+    "INSERT INTO SubscriptionSets VALUES(3,1649541001,'com.apple.Safari','https://www.apple.com',0)"_s,
+    "CREATE TABLE Subscriptions(  rowID INTEGER PRIMARY KEY AUTOINCREMENT,  creationTime INT NOT NULL,  subscriptionSetID INT NOT NULL,  scope TEXT NOT NULL,  endpoint TEXT NOT NULL,  topic TEXT NOT NULL UNIQUE,  serverVAPIDPublicKey BLOB NOT NULL,  clientPublicKey BLOB NOT NULL,  clientPrivateKey BLOB NOT NULL,  sharedAuthSecret BLOB NOT NULL,  expirationTime INT,  UNIQUE(scope, subscriptionSetID))"_s,
+    "INSERT INTO Subscriptions VALUES(1,1649541001,1,'https://www.apple.com/iphone','https://pushEndpoint1','topic1',X'0506',X'0607',X'0708',X'0809',NULL)"_s,
+    "INSERT INTO Subscriptions VALUES(2,1649541001,2,'https://www.webkit.org/blog','https://pushEndpoint2','topic2',X'0e0f',X'1011',X'1213',X'1415',NULL)"_s,
+    "INSERT INTO Subscriptions VALUES(3,1649541001,3,'https://www.apple.com/mac','https://pushEndpoint3','topic3',X'0001',X'0102',X'0203',X'0405',1643350000)"_s,
+    "INSERT INTO Subscriptions VALUES(4,1649541001,3,'https://www.apple.com/iphone','https://pushEndpoint4','topic4',X'090a',X'0a0b',X'0b0c',X'0c0d',NULL)"_s,
+    "DELETE FROM sqlite_sequence"_s,
+    "INSERT INTO sqlite_sequence VALUES('SubscriptionSets',3)"_s,
+    "INSERT INTO sqlite_sequence VALUES('Subscriptions',4)"_s,
+    "CREATE INDEX Subscriptions_SubscriptionSetID_Index ON Subscriptions(subscriptionSetID)"_s,
+    "PRAGMA user_version = 2"_s,
+};
+
+TEST(PushDatabase, CanMigrateV2DatabaseToCurrentSchema)
+{
+    auto path = makeTemporaryDatabasePath();
+    ASSERT_TRUE(createDatabaseFromStatements(path, pushDatabaseV2Statements, std::size(pushDatabaseV2Statements)));
+
+    // Make sure records are there after migrating.
+    {
+        auto databaseResult = createDatabaseSync(path);
+        ASSERT_TRUE(databaseResult);
+        auto& database = *databaseResult;
+
+        auto recordResult = getRecordByBundleIdentifierAndScopeSync(database, "com.apple.Safari"_s, "https://www.webkit.org/blog"_s);
+        ASSERT_TRUE(recordResult);
+        ASSERT_EQ(recordResult->topic, "topic2"_s);
+
+        auto rowIdentifiers = getRowIdentifiersSync(database);
+        ASSERT_EQ(rowIdentifiers, (HashSet<uint64_t> { 1, 2, 3, 4 }));
+
+        // Setting the initial token should return PublicTokenChanged::No.
+        auto updateResult = updatePublicTokenSync(database, Vector<uint8_t> { 'a', 'b' });
+        ASSERT_EQ(updateResult, PushDatabase::PublicTokenChanged::No);
+    }
+
+    // Make sure records are there after re-opening without migration.
+    {
+        auto databaseResult = createDatabaseSync(path);
+        ASSERT_TRUE(databaseResult);
+        auto& database = *databaseResult;
+
+        auto getResult = getPublicTokenSync(database);
+        ASSERT_EQ(getResult, (Vector<uint8_t> { 'a', 'b' }));
+
+        auto rowIdentifiers = getRowIdentifiersSync(database);
+        ASSERT_EQ(rowIdentifiers, (HashSet<uint64_t> { 1, 2, 3, 4 }));
+    }
+}
+
 } // namespace TestWebKitAPI
 
 #endif // ENABLE(SERVICE_WORKER)

Modified: trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/WebPushDaemon.mm (293059 => 293060)


--- trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/WebPushDaemon.mm	2022-04-20 03:05:47 UTC (rev 293059)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/WebPushDaemon.mm	2022-04-20 05:11:55 UTC (rev 293060)
@@ -1090,6 +1090,75 @@
     ASSERT_TRUE([message isEqualToString:@"Unsubscribed"]);
 }
 
+TEST_F(WebPushDTest, GetPushSubscriptionWithMismatchedPublicToken)
+{
+    static constexpr auto htmlSource = R"HTML(
+    <script src=""
+    <script>
+    let postNoteMessage = window.webkit.messageHandlers.note.postMessage.bind(window.webkit.messageHandlers.note);
+    let getPushManager =
+        navigator.serviceWorker.register('/sw.js')
+            .then(() => navigator.serviceWorker.ready)
+            .then(registration => registration.pushManager);
+
+    function subscribe()
+    {
+        getPushManager
+            .then(pushManager => pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: VALID_SERVER_KEY }))
+            .then(subscription => subscription.toJSON())
+            .then(postNoteMessage)
+            .catch(e => postNoteMessage(e.toString()));
+    }
+
+    function getSubscription()
+    {
+        getPushManager
+            .then(pushManager => pushManager.getSubscription())
+            .then(subscription => subscription ? subscription.toJSON() : null)
+            .then(postNoteMessage)
+            .catch(e => postNoteMessage(e.toString()));
+    }
+
+    postNoteMessage('Ready');
+    </script>
+    )HTML"_s;
+
+    __block RetainPtr<id> message = nil;
+    __block bool gotMessage = false;
+    [m_notificationMessageHandler setMessageHandler:^(id receivedMessage) {
+        message = receivedMessage;
+        gotMessage = true;
+    }];
+
+    loadRequest(htmlSource, ""_s);
+    TestWebKitAPI::Util::run(&gotMessage);
+    ASSERT_TRUE([message isEqualToString:@"Ready"]);
+
+    message = nil;
+    gotMessage = false;
+    [m_webView evaluateJavaScript:@"subscribe()" completionHandler:^(id, NSError*) { }];
+    TestWebKitAPI::Util::run(&gotMessage);
+    RetainPtr<id> subscription = message;
+    ASSERT_FALSE([subscription isEqual:[NSNull null]]);
+    ASSERT_TRUE([subscription isKindOfClass:[NSDictionary class]] && [subscription objectForKey:@"endpoint"]);
+
+    message = nil;
+    gotMessage = false;
+    [m_webView evaluateJavaScript:@"getSubscription()" completionHandler:^(id, NSError*) { }];
+    TestWebKitAPI::Util::run(&gotMessage);
+    ASSERT_TRUE([message isEqual:subscription.get()]);
+
+    // If the public token changes, all subscriptions should be invalidated.
+    auto utilityConnection = createAndConfigureConnectionToService("org.webkit.webpushtestdaemon.service");
+    sendMessageToDaemonWaitingForReply(utilityConnection.get(), MessageType::SetPublicTokenForTesting, encodeString("foobar"_s));
+
+    message = nil;
+    gotMessage = false;
+    [m_webView evaluateJavaScript:@"getSubscription()" completionHandler:^(id, NSError*) { }];
+    TestWebKitAPI::Util::run(&gotMessage);
+    ASSERT_TRUE([message isEqual:[NSNull null]]);
+}
+
 } // namespace TestWebKitAPI
 
 #endif // PLATFORM(MAC) || PLATFORM(IOS)
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to