Handle push notification registration e2e

This commit is contained in:
Andrea Maria Piana 2020-07-07 15:55:24 +02:00
parent d985af4a7e
commit 7f6c8db6db
No known key found for this signature in database
GPG Key ID: AA6CCA6DE0E06424
4 changed files with 185 additions and 46 deletions

View File

@ -7,6 +7,7 @@ import (
"io/ioutil"
"math/rand"
"os"
"reflect"
"sync"
"time"
@ -252,7 +253,16 @@ func NewMessenger(
}
pushNotificationClientPersistence := push_notification_client.NewPersistence(database)
pushNotificationClient := push_notification_client.New(pushNotificationClientPersistence, processor)
// Hardcoding for now
pushNotificationClientConfig := &push_notification_client.Config{
Identity: identity,
TokenType: protobuf.PushNotificationRegistration_APN_TOKEN,
SendEnabled: true,
Logger: logger,
RemoteNotificationsEnabled: true,
InstallationID: installationID,
}
pushNotificationClient := push_notification_client.New(pushNotificationClientPersistence, pushNotificationClientConfig, processor)
handler := newMessageHandler(identity, logger, &sqlitePersistence{db: database})
@ -1689,6 +1699,7 @@ func (m *Messenger) syncContact(ctx context.Context, contact *Contact) error {
// RetrieveAll retrieves messages from all filters, processes them and returns a
// MessengerResponse to the client
func (m *Messenger) RetrieveAll() (*MessengerResponse, error) {
m.logger.Info("RETRIEVING ALL", zap.String("installation-id", m.installationID))
chatWithMessages, err := m.transport.RetrieveRawAll()
if err != nil {
return nil, err
@ -1920,18 +1931,6 @@ func (m *Messenger) handleRetrievedMessages(chatWithMessages map[transport.Filte
logger.Warn("failed to handle ContactUpdate", zap.Error(err))
continue
}
case protobuf.PushNotificationRegistration:
logger.Debug("Received PushNotificationRegistration")
if m.pushNotificationServer == nil {
continue
}
logger.Debug("Handling PushNotificationRegistration")
// TODO: Compare DST with Identity
if err := m.pushNotificationServer.HandlePushNotificationRegistration2(publicKey, msg.ParsedMessage.([]byte)); err != nil {
logger.Warn("failed to handle PushNotificationRegistration", zap.Error(err))
}
// We continue in any case, no changes to messenger
continue
case protobuf.PushNotificationQuery:
logger.Debug("Received PushNotificationQuery")
if m.pushNotificationServer == nil {
@ -1958,7 +1957,22 @@ func (m *Messenger) handleRetrievedMessages(chatWithMessages map[transport.Filte
continue
default:
logger.Debug("message not handled")
// Check if is an encrypted PushNotificationRegistration
if msg.Type == protobuf.ApplicationMetadataMessage_PUSH_NOTIFICATION_REGISTRATION {
logger.Debug("Received PushNotificationRegistration")
if m.pushNotificationServer == nil {
continue
}
logger.Debug("Handling PushNotificationRegistration")
// TODO: Compare DST with Identity
if err := m.pushNotificationServer.HandlePushNotificationRegistration2(publicKey, msg.ParsedMessage.([]byte)); err != nil {
logger.Warn("failed to handle PushNotificationRegistration", zap.Error(err))
}
// We continue in any case, no changes to messenger
continue
}
logger.Debug("message not handled", zap.Any("messageType", reflect.TypeOf(msg.ParsedMessage)))
}
}

View File

@ -1,22 +1,25 @@
package push_notification_client
import (
"context"
"crypto/aes"
"crypto/cipher"
"crypto/ecdsa"
"crypto/rand"
"errors"
"io"
"time"
"golang.org/x/crypto/sha3"
"github.com/golang/protobuf/proto"
"github.com/google/uuid"
"github.com/status-im/status-go/eth-node/crypto/ecies"
"github.com/status-im/status-go/protocol/common"
"github.com/status-im/status-go/protocol/protobuf"
"go.uber.org/zap"
)
const encryptedPayloadKeyLength = 16
const accessTokenKeyLength = 16
type PushNotificationServer struct {
@ -46,6 +49,11 @@ type Config struct {
PushNotificationServers []*PushNotificationServer
// InstallationID is the installation-id for this device
InstallationID string
Logger *zap.Logger
// TokenType is the type of token
TokenType protobuf.PushNotificationRegistration_TokenType
}
type Client struct {
@ -67,14 +75,19 @@ type Client struct {
//messageProcessor is a message processor used to send and being notified of messages
messageProcessor *common.MessageProcessor
//pushNotificationQueryResponses is a channel that listens to pushNotificationResponse
pushNotificationQueryResponses chan *protobuf.PushNotificationQueryResponse
}
func New(persistence *Persistence, processor *common.MessageProcessor) *Client {
func New(persistence *Persistence, config *Config, processor *common.MessageProcessor) *Client {
return &Client{
quit: make(chan struct{}),
messageProcessor: processor,
persistence: persistence,
reader: rand.Reader}
quit: make(chan struct{}),
config: config,
pushNotificationQueryResponses: make(chan *protobuf.PushNotificationQueryResponse),
messageProcessor: processor,
persistence: persistence,
reader: rand.Reader}
}
func (c *Client) Start() error {
@ -129,7 +142,7 @@ func (p *Client) mutedChatIDsHashes(chatIDs []string) [][]byte {
var mutedChatListHashes [][]byte
for _, chatID := range chatIDs {
mutedChatListHashes = append(mutedChatListHashes, shake256(chatID))
mutedChatListHashes = append(mutedChatListHashes, common.Shake256([]byte(chatID)))
}
return mutedChatListHashes
@ -174,6 +187,7 @@ func (p *Client) buildPushNotificationRegistrationMessage(contactIDs []*ecdsa.Pu
options := &protobuf.PushNotificationRegistration{
AccessToken: token,
TokenType: p.config.TokenType,
Version: p.lastPushNotificationVersion + 1,
InstallationId: p.config.InstallationID,
Token: p.DeviceToken,
@ -184,15 +198,53 @@ func (p *Client) buildPushNotificationRegistrationMessage(contactIDs []*ecdsa.Pu
return options, nil
}
func (p *Client) Register(deviceToken string, contactIDs []*ecdsa.PublicKey, mutedChatIDs []string) error {
servers, err := p.persistence.GetServers()
func (c *Client) Register(deviceToken string, contactIDs []*ecdsa.PublicKey, mutedChatIDs []string) error {
c.DeviceToken = deviceToken
servers, err := c.persistence.GetServers()
if err != nil {
return err
}
if len(servers) == 0 {
return errors.New("no servers to register with")
}
return nil
registration, err := c.buildPushNotificationRegistrationMessage(contactIDs, mutedChatIDs)
if err != nil {
return err
}
marshaledRegistration, err := proto.Marshal(registration)
if err != nil {
return err
}
for _, server := range servers {
encryptedRegistration, err := c.encryptRegistration(server.publicKey, marshaledRegistration)
if err != nil {
return err
}
rawMessage := &common.RawMessage{
Payload: encryptedRegistration,
MessageType: protobuf.ApplicationMetadataMessage_PUSH_NOTIFICATION_REGISTRATION,
}
_, err = c.messageProcessor.SendPrivate(context.Background(), server.publicKey, rawMessage)
// Send message and wait for reply
}
for {
select {
case <-c.quit:
return nil
case <-time.After(5 * time.Second):
return errors.New("no query response received")
case <-c.pushNotificationQueryResponses:
return nil
}
}
}
// HandlePushNotificationRegistrationResponse should check whether the response was successful or not, retry if necessary otherwise store the result in the database
@ -206,7 +258,14 @@ func (p *Client) HandlePushNotificationAdvertisement(info *protobuf.PushNotifica
}
// HandlePushNotificationQueryResponse should update the data in the database for a given user
func (p *Client) HandlePushNotificationQueryResponse(response *protobuf.PushNotificationQueryResponse) error {
func (c *Client) HandlePushNotificationQueryResponse(response *protobuf.PushNotificationQueryResponse) error {
c.config.Logger.Debug("received push notification query response", zap.Any("response", response))
select {
case c.pushNotificationQueryResponses <- response:
default:
return errors.New("could not process push notification query response")
}
return nil
}
@ -216,6 +275,7 @@ func (p *Client) HandlePushNotificationResponse(ack *protobuf.PushNotificationRe
}
func (c *Client) AddPushNotificationServer(publicKey *ecdsa.PublicKey) error {
c.config.Logger.Debug("adding push notification server", zap.Any("public-key", publicKey))
currentServers, err := c.persistence.GetServers()
if err != nil {
return err
@ -270,9 +330,19 @@ func encryptAccessToken(plaintext []byte, key []byte, reader io.Reader) ([]byte,
return gcm.Seal(nonce, nonce, plaintext, nil), nil
}
func shake256(input string) []byte {
buf := []byte(input)
h := make([]byte, 64)
sha3.ShakeSum256(h, buf)
return h
func (c *Client) encryptRegistration(publicKey *ecdsa.PublicKey, payload []byte) ([]byte, error) {
sharedKey, err := c.generateSharedKey(publicKey)
if err != nil {
return nil, err
}
return common.Encrypt(payload, sharedKey, c.reader)
}
func (c *Client) generateSharedKey(publicKey *ecdsa.PublicKey) ([]byte, error) {
return ecies.ImportECDSA(c.config.Identity).GenerateShared(
ecies.ImportECDSAPublic(publicKey),
encryptedPayloadKeyLength,
encryptedPayloadKeyLength,
)
}

View File

@ -203,14 +203,14 @@ func (p *Server) HandlePushNotificationRequest(request *protobuf.PushNotificatio
return response
}
func (p *Server) HandlePushNotificationRegistration(publicKey *ecdsa.PublicKey, payload []byte) *protobuf.PushNotificationRegistrationResponse {
func (s *Server) HandlePushNotificationRegistration(publicKey *ecdsa.PublicKey, payload []byte) *protobuf.PushNotificationRegistrationResponse {
s.config.Logger.Debug("handling push notification registration")
response := &protobuf.PushNotificationRegistrationResponse{
RequestId: common.Shake256(payload),
}
registration, err := p.ValidateRegistration(publicKey, payload)
if registration != nil {
}
registration, err := s.ValidateRegistration(publicKey, payload)
if err != nil {
if err == ErrInvalidPushNotificationRegistrationVersion {
@ -218,6 +218,7 @@ func (p *Server) HandlePushNotificationRegistration(publicKey *ecdsa.PublicKey,
} else {
response.Error = protobuf.PushNotificationRegistrationResponse_MALFORMED_MESSAGE
}
s.config.Logger.Warn("registration did not validate", zap.Error(err))
return response
}
@ -227,18 +228,22 @@ func (p *Server) HandlePushNotificationRegistration(publicKey *ecdsa.PublicKey,
Version: registration.Version,
InstallationId: registration.InstallationId,
}
if err := p.persistence.SavePushNotificationRegistration(common.HashPublicKey(publicKey), emptyRegistration); err != nil {
if err := s.persistence.SavePushNotificationRegistration(common.HashPublicKey(publicKey), emptyRegistration); err != nil {
response.Error = protobuf.PushNotificationRegistrationResponse_INTERNAL_ERROR
s.config.Logger.Error("failed to unregister ", zap.Error(err))
return response
}
} else if err := p.persistence.SavePushNotificationRegistration(common.HashPublicKey(publicKey), registration); err != nil {
} else if err := s.persistence.SavePushNotificationRegistration(common.HashPublicKey(publicKey), registration); err != nil {
response.Error = protobuf.PushNotificationRegistrationResponse_INTERNAL_ERROR
s.config.Logger.Error("failed to save registration", zap.Error(err))
return response
}
response.Success = true
s.config.Logger.Debug("handled push notification registration successfully")
return response
}

View File

@ -6,6 +6,7 @@ import (
"io/ioutil"
"os"
"testing"
"time"
"github.com/google/uuid"
"github.com/stretchr/testify/suite"
@ -14,6 +15,7 @@ import (
gethbridge "github.com/status-im/status-go/eth-node/bridge/geth"
"github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/protocol/push_notification_server"
"github.com/status-im/status-go/protocol/tt"
"github.com/status-im/status-go/whisper/v6"
)
@ -46,16 +48,10 @@ func (s *MessengerPushNotificationSuite) SetupTest() {
s.privateKey = s.m.identity
}
func (s *MessengerPushNotificationSuite) newMessengerWithKey(shh types.Whisper, privateKey *ecdsa.PrivateKey) *Messenger {
func (s *MessengerPushNotificationSuite) newMessengerWithOptions(shh types.Whisper, privateKey *ecdsa.PrivateKey, options []Option) *Messenger {
tmpFile, err := ioutil.TempFile("", "")
s.Require().NoError(err)
options := []Option{
WithCustomLogger(s.logger),
WithMessagesPersistenceEnabled(),
WithDatabaseConfig(tmpFile.Name(), "some-key"),
WithDatasync(),
}
m, err := NewMessenger(
privateKey,
&testNode{shh: shh},
@ -72,6 +68,19 @@ func (s *MessengerPushNotificationSuite) newMessengerWithKey(shh types.Whisper,
return m
}
func (s *MessengerPushNotificationSuite) newMessengerWithKey(shh types.Whisper, privateKey *ecdsa.PrivateKey) *Messenger {
tmpFile, err := ioutil.TempFile("", "")
s.Require().NoError(err)
options := []Option{
WithCustomLogger(s.logger),
WithMessagesPersistenceEnabled(),
WithDatabaseConfig(tmpFile.Name(), "some-key"),
WithDatasync(),
}
return s.newMessengerWithOptions(shh, privateKey, options)
}
func (s *MessengerPushNotificationSuite) newMessenger(shh types.Whisper) *Messenger {
privateKey, err := crypto.GenerateKey()
s.Require().NoError(err)
@ -79,16 +88,57 @@ func (s *MessengerPushNotificationSuite) newMessenger(shh types.Whisper) *Messen
return s.newMessengerWithKey(s.shh, privateKey)
}
func (s *MessengerPushNotificationSuite) newPushNotificationServer(shh types.Whisper) *Messenger {
privateKey, err := crypto.GenerateKey()
s.Require().NoError(err)
tmpFile, err := ioutil.TempFile("", "")
s.Require().NoError(err)
serverConfig := &push_notification_server.Config{
Logger: s.logger,
Identity: privateKey,
}
options := []Option{
WithCustomLogger(s.logger),
WithMessagesPersistenceEnabled(),
WithDatabaseConfig(tmpFile.Name(), "some-key"),
WithPushNotificationServerConfig(serverConfig),
WithDatasync(),
}
return s.newMessengerWithOptions(shh, privateKey, options)
}
func (s *MessengerPushNotificationSuite) TestReceivePushNotification() {
errChan := make(chan error)
deviceToken := "token"
server := s.newMessenger(s.shh)
server := s.newPushNotificationServer(s.shh)
client2 := s.newMessenger(s.shh)
err := s.m.AddPushNotificationServer(context.Background(), &server.identity.PublicKey)
s.Require().NoError(err)
err = s.m.RegisterForPushNotifications(context.Background(), deviceToken)
go func() {
err := s.m.RegisterForPushNotifications(context.Background(), deviceToken)
errChan <- err
}()
time.Sleep(500 * time.Millisecond)
_, err = server.RetrieveAll()
s.Require().NoError(err)
time.Sleep(500 * time.Millisecond)
_, err = server.RetrieveAll()
s.Require().NoError(err)
time.Sleep(500 * time.Millisecond)
_, err = server.RetrieveAll()
s.Require().NoError(err)
err = <-errChan
s.Require().NoError(err)
info, err := client2.pushNotificationClient.RetrievePushNotificationInfo(&s.m.identity.PublicKey)