Save response from push notification

This commit is contained in:
Andrea Maria Piana 2020-07-20 12:01:42 +02:00
parent 52e7089e39
commit aa5aa63342
No known key found for this signature in database
GPG Key ID: AA6CCA6DE0E06424
6 changed files with 171 additions and 42 deletions

View File

@ -67,6 +67,19 @@ type PushNotificationInfo struct {
Version uint64
}
type SentNotification struct {
PublicKey *ecdsa.PublicKey
InstallationID string
SentAt int64
MessageID []byte
Success bool
Error protobuf.PushNotificationReport_ErrorType
}
func (s *SentNotification) HashedPublicKey() []byte {
return common.HashPublicKey(s.PublicKey)
}
type Config struct {
// Identity is our identity key
Identity *ecdsa.PrivateKey
@ -197,6 +210,35 @@ func (c *Client) Stop() error {
return nil
}
func (c *Client) queryNotificationInfo(publicKey *ecdsa.PublicKey) error {
// Check if we queried recently
queriedAt, err := c.persistence.GetQueriedAt(publicKey)
if err != nil {
return err
}
// Naively query again if too much time has passed.
// Here it might not be necessary
if time.Now().Unix()-queriedAt > staleQueryTimeInSeconds {
c.config.Logger.Info("querying info")
err := c.QueryPushNotificationInfo(publicKey)
if err != nil {
c.config.Logger.Error("could not query pn info", zap.Error(err))
return err
}
// This is just horrible, but for now will do,
// the issue is that we don't really know how long it will
// take to reply, as there might be multiple servers
// replying to us.
// The only time we are 100% certain that we can proceed is
// when we have non-stale info for each device, but
// most devices are not going to be registered, so we'd still
// have to wait teh maximum amount of time allowed.
time.Sleep(3 * time.Second)
}
return nil
}
func (c *Client) HandleMessageSent(sentMessage *common.SentMessage) error {
c.config.Logger.Info("sent message", zap.Any("sent message", sentMessage))
if !c.config.SendEnabled {
@ -252,33 +294,10 @@ func (c *Client) HandleMessageSent(sentMessage *common.SentMessage) error {
c.config.Logger.Info("actionable messages", zap.Any("message-ids", trackedMessageIDs), zap.Any("installation-ids", installationIDs))
// Check if we queried recently
queriedAt, err := c.persistence.GetQueriedAt(publicKey)
err := c.queryNotificationInfo(publicKey)
if err != nil {
return err
}
// Naively query again if too much time has passed.
// Here it might not be necessary
if time.Now().Unix()-queriedAt > staleQueryTimeInSeconds {
c.config.Logger.Info("querying info")
err := c.QueryPushNotificationInfo(publicKey)
if err != nil {
c.config.Logger.Error("could not query pn info", zap.Error(err))
return err
}
// This is just horrible, but for now will do,
// the issue is that we don't really know how long it will
// take to reply, as there might be multiple servers
// replying to us.
// The only time we are 100% certain that we can proceed is
// when we have non-stale info for each device, but
// most devices are not going to be registered, so we'd still
// have to wait teh maximum amount of time allowed.
time.Sleep(3 * time.Second)
}
c.config.Logger.Info("queried info")
// Retrieve infos
info, err := c.GetPushNotificationInfo(publicKey, installationIDs)
@ -380,7 +399,12 @@ func (c *Client) shouldNotifyOn(publicKey *ecdsa.PublicKey, installationID strin
}
func (c *Client) notifiedOn(publicKey *ecdsa.PublicKey, installationID string, messageID []byte) error {
return c.persistence.NotifiedOn(publicKey, installationID, messageID)
return c.persistence.NotifiedOn(&SentNotification{
PublicKey: publicKey,
SentAt: time.Now().Unix(),
InstallationID: installationID,
MessageID: messageID,
})
}
func (p *Client) mutedChatIDsHashes(chatIDs []string) [][]byte {
var mutedChatListHashes [][]byte
@ -850,7 +874,14 @@ func (c *Client) HandlePushNotificationQueryResponse(serverPublicKey *ecdsa.Publ
}
// HandlePushNotificationResponse should set the request as processed
func (p *Client) HandlePushNotificationResponse(serverKey *ecdsa.PublicKey, response protobuf.PushNotificationResponse) error {
func (c *Client) HandlePushNotificationResponse(serverKey *ecdsa.PublicKey, response protobuf.PushNotificationResponse) error {
messageID := response.MessageId
for _, report := range response.Reports {
err := c.persistence.UpdateNotificationResponse(messageID, report)
if err != nil {
return err
}
}
return nil
}

View File

@ -144,13 +144,14 @@ func (s *ClientSuite) TestBuildPushNotificationRegisterMessageAllowFromContactsO
s.client.reader = bytes.NewReader([]byte(expectedUUID))
options := &protobuf.PushNotificationRegistration{
Version: 1,
AccessToken: expectedUUID,
Token: myDeviceToken,
InstallationId: s.installationID,
Enabled: true,
BlockedChatList: mutedChatListHashes,
AllowedUserList: [][]byte{encryptedToken},
Version: 1,
AccessToken: expectedUUID,
Token: myDeviceToken,
InstallationId: s.installationID,
AllowFromContactsOnly: true,
Enabled: true,
BlockedChatList: mutedChatListHashes,
AllowedUserList: [][]byte{encryptedToken},
}
actualMessage, err := s.client.buildPushNotificationRegistrationMessage(contactIDs, mutedChatList)

View File

@ -1,7 +1,7 @@
// Code generated by go-bindata. DO NOT EDIT.
// sources:
// 1593601729_initial_schema.down.sql (144B)
// 1593601729_initial_schema.up.sql (1.6kB)
// 1593601729_initial_schema.up.sql (1.709kB)
// doc.go (382B)
package migrations
@ -91,7 +91,7 @@ func _1593601729_initial_schemaDownSql() (*asset, error) {
return a, nil
}
var __1593601729_initial_schemaUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xac\x94\xc1\x6e\xa3\x30\x10\x86\xef\x3c\xc5\x1c\x1b\x29\x87\xbd\xf7\x44\xa8\x59\x21\x59\xf6\x6e\x62\xa4\xdc\x2c\xaf\x71\x1b\x2b\xac\xe9\xda\xa6\x5a\xde\x7e\x65\x08\x29\xa9\x59\xa8\xda\x5e\x22\x65\xfc\x7b\xf0\x37\xf3\xcf\x64\x7b\x94\x32\x04\x2c\xdd\x61\x04\x45\x0e\x84\x32\x40\xc7\xe2\xc0\x0e\xf0\xdc\xba\x13\x37\x8d\xd7\x8f\x5a\x0a\xaf\x1b\xc3\x65\xad\x95\xf1\xdc\x29\xfb\xa2\xac\x83\xbb\x04\xe0\xb9\xfd\x55\x6b\xc9\xcf\xaa\x83\x1d\xa6\xbb\xfe\x3e\x29\x31\xde\x26\x00\x56\x3d\x69\xe7\x95\x55\x15\xec\x28\xc5\x28\x25\xf0\x80\xf2\xb4\xc4\x0c\xf2\x14\x1f\xd0\xad\x86\x0b\x0f\x05\x61\xd7\x0c\x57\xed\xb7\xa0\xab\x85\xf3\xdc\x2a\x6f\xf5\x9a\x32\x88\x3a\x2e\x9b\xd6\x2c\xa9\x84\x94\xca\x39\xee\x9b\xb3\x32\xc0\xd0\x91\x85\x60\x49\x8a\x9f\x25\xba\x7b\x65\xda\x00\x25\x90\x51\x92\xe3\x22\x63\xb0\x47\x3f\x70\x9a\xa1\x64\x73\x9f\x24\x1f\xa9\xdb\x9f\x56\x59\xad\xd6\xeb\x36\xe8\x22\xcc\xf1\xa8\xe3\xba\x8a\x2f\x45\x6f\xdf\x8e\xda\xaf\x85\xd0\xe6\xb1\x59\x25\x18\x1c\xc2\x97\x24\xda\x38\x2f\xea\x7a\xc8\xad\xab\xbe\x07\x37\x82\xa8\x43\x6f\xbc\x15\xac\xf0\x32\x5f\xa5\xe0\x4e\xdd\x98\x28\x1e\xd7\xe8\xed\x33\xb6\xf1\xd3\xbf\xb6\x7c\xde\x0a\x79\x56\x15\xff\xad\x9c\x13\x4f\x17\x33\x5c\xfe\xcc\xf6\x55\x9e\x84\x9f\xad\xcf\x98\x69\x86\xff\xc2\xf9\x9a\xf6\x96\xa1\xf8\x4e\xe8\x1e\x25\x00\x1f\x85\x70\xe1\x67\x7a\xb0\x8e\xf1\x29\x2b\xf4\xdf\x7b\x0f\xe7\x16\x16\x7a\xbb\xf9\x04\xf1\xb0\xa6\xec\x04\x76\x5c\x5d\x43\x2c\x86\x02\x90\x8d\xf1\x42\x86\xe6\xb9\xfe\x78\x88\xba\xce\xf8\x93\xf2\x5a\x06\xd2\xff\xef\xa7\x2b\xdc\x54\xbf\x6a\xc5\x82\x3c\xa0\x23\xe8\xea\x2f\x5f\x9c\xdf\xe9\x60\x52\xb2\x3c\xeb\x4b\xd3\xb2\xb9\x4f\xfe\x05\x00\x00\xff\xff\x2b\xa6\x15\x32\x40\x06\x00\x00")
var __1593601729_initial_schemaUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xac\x54\xc1\x6e\xe2\x30\x10\xbd\xe7\x2b\xe6\x58\x24\x0e\x7b\xef\x29\x50\xb3\x8a\x64\x39\xbb\x60\x24\x6e\x96\xd7\x99\x36\x16\x59\xa7\x6b\x3b\xd5\xf2\xf7\x2b\x27\x90\x42\x9d\x75\xa4\x96\x0b\x12\x33\xcf\xa3\x79\x6f\x5e\xde\x7a\x4b\x72\x4e\x80\xe7\x2b\x4a\xa0\xd8\x00\x2b\x39\x90\x43\xb1\xe3\x3b\x78\xed\x5c\x2d\x4c\xeb\xf5\xb3\x56\xd2\xeb\xd6\x08\xd5\x68\x34\x5e\x38\xb4\x6f\x68\x1d\x3c\x64\x00\xaf\xdd\xaf\x46\x2b\x71\xc4\x13\xac\x68\xb9\xea\xdf\xb3\x3d\xa5\xcb\x0c\xc0\xe2\x8b\x76\x1e\x2d\x56\xb0\x2a\x4b\x4a\x72\x06\x4f\x64\x93\xef\x29\x87\x4d\x4e\x77\xe4\x16\x23\xa4\x87\x82\xf1\x71\xc2\x88\xfd\x16\x70\x8d\x74\x5e\x58\xf4\x56\xcf\x21\x03\xe8\x24\x54\xdb\x99\x14\x4a\x2a\x85\xce\x09\xdf\x1e\xd1\x00\x27\x07\x1e\x8a\x7b\x56\xfc\xdc\x93\x87\x77\x4e\x0b\x28\x19\xac\x4b\xb6\xa1\xc5\x9a\xc3\x96\xfc\xa0\xf9\x9a\x64\x8b\xc7\x2c\xfb\x8c\x6e\x7f\x3a\xb4\x1a\xe7\x75\x1b\x70\x11\xcd\x4b\xeb\x24\x74\x15\x3f\x8a\x76\x5f\x5e\xb0\xf7\x25\xa1\xcd\x73\x3b\xcb\x60\x70\x88\x48\x41\xb4\x71\x5e\x36\xcd\x30\x5b\x57\xfd\x0d\x6e\x00\xd1\x85\x3e\x78\x2b\x58\xe1\x6d\x5a\xa5\xe0\x4e\xdd\x9a\xa8\x1e\x6b\xf4\x71\x8d\x65\xbc\xfa\x7d\xe5\xf3\x56\xaa\x23\x56\xe2\x37\x3a\x27\x5f\xce\x66\x38\xff\x99\xbc\xab\xaa\xa5\x9f\xd4\xe7\x32\x69\x82\xff\x99\xe7\xfb\xd8\x5b\x0e\xc5\x77\x56\x6e\x49\x06\xf0\x59\x12\x2e\xfc\x5c\x37\xe6\x69\xa4\xac\x50\x4b\x57\x63\xf5\x35\xb7\xf4\x2b\x4d\x48\xe1\xba\xde\x46\x63\x00\x45\x69\x30\x26\x11\x5a\xdb\xda\x44\x62\x44\xa2\x2e\x21\x61\xa4\xc5\x17\xe4\x1d\x32\xd1\x5e\x29\x7b\xc9\xc9\xa1\x16\xcb\x03\xa0\x5a\xe3\xa5\x0a\x4e\x71\x7d\x7b\xa8\xba\x93\xf1\x35\x7a\xad\x82\x66\xff\xa7\x36\x92\xbb\xc6\xcf\xfa\xbe\x60\x4f\xe4\x00\xba\xfa\x2b\x92\x61\x71\x7d\xd7\x92\xa5\x83\x25\xf5\x69\x2e\x1e\xb3\x7f\x01\x00\x00\xff\xff\xed\x10\xc3\xcd\xad\x06\x00\x00")
func _1593601729_initial_schemaUpSqlBytes() ([]byte, error) {
return bindataRead(
@ -106,8 +106,8 @@ func _1593601729_initial_schemaUpSql() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "1593601729_initial_schema.up.sql", size: 1600, mode: os.FileMode(0644), modTime: time.Unix(1594978360, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xd, 0x57, 0x6b, 0xc6, 0x63, 0x1c, 0x3a, 0x78, 0xa0, 0xc3, 0x12, 0x63, 0xbf, 0x34, 0x6c, 0x86, 0xd2, 0xce, 0x6c, 0xfb, 0xdd, 0xa8, 0x44, 0x5c, 0x0, 0x4e, 0x1a, 0x99, 0xa1, 0xfc, 0xf5, 0x3b}}
info := bindataFileInfo{name: "1593601729_initial_schema.up.sql", size: 1709, mode: os.FileMode(0644), modTime: time.Unix(1595237467, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x35, 0x40, 0x6a, 0x4a, 0x45, 0x37, 0x37, 0x99, 0x97, 0x5, 0xb3, 0x43, 0x6, 0x43, 0xcc, 0x10, 0x32, 0xbc, 0x16, 0xcc, 0xe0, 0xfb, 0x3, 0xa8, 0xce, 0x6a, 0x6b, 0x39, 0xd4, 0xe0, 0xbe, 0xa4}}
return a, nil
}

View File

@ -35,8 +35,11 @@ CREATE TABLE IF NOT EXISTS push_notification_client_tracked_messages (
CREATE TABLE IF NOT EXISTS push_notification_client_sent_notifications (
message_id BLOB NOT NULL,
public_key BLOB NOT NULL,
hashed_public_key BLOB NOT NULL,
installation_id TEXT NOT NULL,
sent_at INT NOT NULL,
success BOOLEAN NOT NULL DEFAULT FALSE,
error INT NOT NULL DEFAULT 0,
UNIQUE(message_id, public_key, installation_id)
);

View File

@ -42,7 +42,7 @@ func (p *Persistence) GetLastPushNotificationRegistration() (*protobuf.PushNotif
return nil, nil, err
}
for _, pkBytes := range publicKeyBytes {
pk, err := crypto.UnmarshalPubkey(pkBytes)
pk, err := crypto.DecompressPubkey(pkBytes)
if err != nil {
return nil, nil, err
}
@ -63,7 +63,7 @@ func (p *Persistence) SaveLastPushNotificationRegistration(registration *protobu
var encodedContactIDs bytes.Buffer
var contactIDsBytes [][]byte
for _, pk := range contactIDs {
contactIDsBytes = append(contactIDsBytes, crypto.FromECDSAPub(pk))
contactIDsBytes = append(contactIDsBytes, crypto.CompressPubkey(pk))
}
pkEncoder := gob.NewEncoder(&encodedContactIDs)
if err := pkEncoder.Encode(contactIDsBytes); err != nil {
@ -271,9 +271,34 @@ func (p *Persistence) ShouldSendNotificationToAllInstallationIDs(publicKey *ecds
return count == 0, nil
}
func (p *Persistence) NotifiedOn(publicKey *ecdsa.PublicKey, installationID string, messageID []byte) error {
sentAt := time.Now().Unix()
_, err := p.db.Exec(`INSERT INTO push_notification_client_sent_notifications (public_key, installation_id, message_id, sent_at) VALUES (?, ?, ?, ?)`, crypto.CompressPubkey(publicKey), installationID, messageID, sentAt)
func (p *Persistence) NotifiedOn(n *SentNotification) error {
_, err := p.db.Exec(`INSERT INTO push_notification_client_sent_notifications (public_key, installation_id, message_id, sent_at, hashed_public_key) VALUES (?, ?, ?, ?, ?)`, crypto.CompressPubkey(n.PublicKey), n.InstallationID, n.MessageID, n.SentAt, n.HashedPublicKey())
return err
}
func (p *Persistence) GetSentNotification(hashedPublicKey []byte, installationID string, messageID []byte) (*SentNotification, error) {
var publicKeyBytes []byte
sentNotification := &SentNotification{
InstallationID: installationID,
MessageID: messageID,
}
err := p.db.QueryRow(`SELECT sent_at, error, success, public_key FROM push_notification_client_sent_notifications WHERE hashed_public_key = ?`, hashedPublicKey).Scan(&sentNotification.SentAt, &sentNotification.Error, &sentNotification.Success, &publicKeyBytes)
if err != nil {
return nil, err
}
publicKey, err := crypto.DecompressPubkey(publicKeyBytes)
if err != nil {
return nil, err
}
sentNotification.PublicKey = publicKey
return sentNotification, nil
}
func (p *Persistence) UpdateNotificationResponse(messageID []byte, response *protobuf.PushNotificationReport) error {
_, err := p.db.Exec(`UPDATE push_notification_client_sent_notifications SET success = ?, error = ? WHERE hashed_public_key = ? AND installation_id = ? AND message_id = ? AND NOT success`, response.Success, response.Error, response.PublicKey, response.InstallationId, messageID)
return err
}

View File

@ -5,6 +5,7 @@ import (
"io/ioutil"
"os"
"testing"
"time"
"github.com/golang/protobuf/proto"
"github.com/stretchr/testify/suite"
@ -211,6 +212,74 @@ func (s *SQLitePersistenceSuite) TestSaveAndRetrieveInfoWithVersion() {
s.Require().Equal(uint64(2), retrievedInfos[0].Version)
}
func (s *SQLitePersistenceSuite) TestNotifiedOnAndUpdateNotificationResponse() {
key, err := crypto.GenerateKey()
s.Require().NoError(err)
installationID := "installation-id"
messageID := []byte("message-id")
sentNotification := &SentNotification{
PublicKey: &key.PublicKey,
InstallationID: installationID,
MessageID: messageID,
SentAt: time.Now().Unix(),
}
s.Require().NoError(s.persistence.NotifiedOn(sentNotification))
retrievedNotification, err := s.persistence.GetSentNotification(sentNotification.HashedPublicKey(), installationID, messageID)
s.Require().NoError(err)
s.Require().Equal(sentNotification, retrievedNotification)
response := &protobuf.PushNotificationReport{
Success: false,
Error: protobuf.PushNotificationReport_WRONG_TOKEN,
PublicKey: sentNotification.HashedPublicKey(),
InstallationId: installationID,
}
s.Require().NoError(s.persistence.UpdateNotificationResponse(messageID, response))
sentNotification.Error = protobuf.PushNotificationReport_WRONG_TOKEN
retrievedNotification, err = s.persistence.GetSentNotification(sentNotification.HashedPublicKey(), installationID, messageID)
s.Require().NoError(err)
s.Require().Equal(sentNotification, retrievedNotification)
// Update with a successful notification
response = &protobuf.PushNotificationReport{
Success: true,
PublicKey: sentNotification.HashedPublicKey(),
InstallationId: installationID,
}
s.Require().NoError(s.persistence.UpdateNotificationResponse(messageID, response))
sentNotification.Success = true
sentNotification.Error = protobuf.PushNotificationReport_UNKNOWN_ERROR_TYPE
retrievedNotification, err = s.persistence.GetSentNotification(sentNotification.HashedPublicKey(), installationID, messageID)
s.Require().NoError(err)
s.Require().Equal(sentNotification, retrievedNotification)
// Update with a unsuccessful notification, it should be ignored
response = &protobuf.PushNotificationReport{
Success: false,
Error: protobuf.PushNotificationReport_WRONG_TOKEN,
PublicKey: sentNotification.HashedPublicKey(),
InstallationId: installationID,
}
s.Require().NoError(s.persistence.UpdateNotificationResponse(messageID, response))
sentNotification.Success = true
sentNotification.Error = protobuf.PushNotificationReport_UNKNOWN_ERROR_TYPE
retrievedNotification, err = s.persistence.GetSentNotification(sentNotification.HashedPublicKey(), installationID, messageID)
s.Require().NoError(err)
s.Require().Equal(sentNotification, retrievedNotification)
}
func (s *SQLitePersistenceSuite) TestSaveAndRetrieveRegistration() {
// Try with nil first
retrievedRegistration, retrievedContactIDs, err := s.persistence.GetLastPushNotificationRegistration()