Implement server persistence for client & basic tests

This commit is contained in:
Andrea Maria Piana 2020-07-07 11:00:04 +02:00
parent 3afde67022
commit d985af4a7e
No known key found for this signature in database
GPG Key ID: AA6CCA6DE0E06424
19 changed files with 526 additions and 115 deletions

View File

@ -1,19 +1,24 @@
package push_notification_server
package common
import (
"crypto/aes"
"crypto/cipher"
"crypto/ecdsa"
"errors"
"github.com/status-im/status-go/eth-node/crypto"
"golang.org/x/crypto/sha3"
"io"
)
func hashPublicKey(pk *ecdsa.PublicKey) []byte {
return shake256(crypto.CompressPubkey(pk))
const nonceLength = 12
var ErrInvalidCiphertextLength = errors.New("invalid cyphertext length")
func HashPublicKey(pk *ecdsa.PublicKey) []byte {
return Shake256(crypto.CompressPubkey(pk))
}
func decrypt(cyphertext []byte, key []byte) ([]byte, error) {
func Decrypt(cyphertext []byte, key []byte) ([]byte, error) {
if len(cyphertext) < nonceLength {
return nil, ErrInvalidCiphertextLength
}
@ -32,7 +37,7 @@ func decrypt(cyphertext []byte, key []byte) ([]byte, error) {
return gcm.Open(nil, nonce, cyphertext[nonceLength:], nil)
}
func encrypt(plaintext []byte, key []byte, reader io.Reader) ([]byte, error) {
func Encrypt(plaintext []byte, key []byte, reader io.Reader) ([]byte, error) {
c, err := aes.NewCipher(key)
if err != nil {
return nil, err
@ -51,8 +56,14 @@ func encrypt(plaintext []byte, key []byte, reader io.Reader) ([]byte, error) {
return gcm.Seal(nonce, nonce, plaintext, nil), nil
}
func shake256(buf []byte) []byte {
func Shake256(buf []byte) []byte {
h := make([]byte, 64)
sha3.ShakeSum256(h, buf)
return h
}
// IsPubKeyEqual checks that two public keys are equal
func IsPubKeyEqual(a, b *ecdsa.PublicKey) bool {
// the curve is always the same, just compare the points
return a.X.Cmp(b.X) == 0 && a.Y.Cmp(b.Y) == 0
}

View File

@ -482,9 +482,3 @@ func calculatePoW(payload []byte) float64 {
}
return whisperDefaultPoW
}
// IsPubKeyEqual checks that two public keys are equal
func IsPubKeyEqual(a, b *ecdsa.PublicKey) bool {
// the curve is always the same, just compare the points
return a.X.Cmp(b.X) == 0 && a.Y.Cmp(b.Y) == 0
}

View File

@ -72,6 +72,7 @@ type Messenger struct {
allInstallations map[string]*multidevice.Installation
modifiedInstallations map[string]bool
installationID string
mailserver []byte
mutex sync.Mutex
}
@ -2036,13 +2037,19 @@ func (m *Messenger) handleRetrievedMessages(chatWithMessages map[transport.Filte
return messageState.Response, nil
}
func (m *Messenger) SetMailserver(peer []byte) {
m.mailserver = peer
}
func (m *Messenger) RequestHistoricMessages(
ctx context.Context,
peer []byte, // should be removed after mailserver logic is ported
from, to uint32,
cursor []byte,
) ([]byte, error) {
return m.transport.SendMessagesRequest(ctx, peer, from, to, cursor)
if m.mailserver == nil {
return nil, errors.New("no mailserver selected")
}
return m.transport.SendMessagesRequest(ctx, m.mailserver, from, to, cursor)
}
func (m *Messenger) LoadFilters(filters []*transport.Filter) ([]*transport.Filter, error) {
@ -2965,6 +2972,46 @@ func (m *Messenger) Timesource() TimeSource {
return m.getTimesource()
}
// AddPushNotificationServer adds a push notification server
func (m *Messenger) AddPushNotificationServer(ctx context.Context, publicKey *ecdsa.PublicKey) error {
if m.pushNotificationClient == nil {
return errors.New("push notification client not enabled")
}
return m.pushNotificationClient.AddPushNotificationServer(publicKey)
}
// RegisterForPushNotification register deviceToken with any push notification server enabled
func (m *Messenger) RegisterForPushNotifications(ctx context.Context, deviceToken string) error {
if m.pushNotificationClient == nil {
return errors.New("push notification client not enabled")
}
var contactIDs []*ecdsa.PublicKey
var mutedChatIDs []string
m.mutex.Lock()
for _, contact := range m.allContacts {
if contact.IsAdded() {
pk, err := contact.PublicKey()
if err != nil {
m.logger.Warn("could not parse contact public key")
continue
}
contactIDs = append(contactIDs, pk)
} else if contact.IsBlocked() {
mutedChatIDs = append(mutedChatIDs, contact.ID)
}
}
for _, chat := range m.allChats {
if chat.Muted {
mutedChatIDs = append(mutedChatIDs, chat.ID)
}
}
m.mutex.Unlock()
return m.pushNotificationClient.Register(deviceToken, contactIDs, mutedChatIDs)
}
func generateAliasAndIdenticon(pk string) (string, string, error) {
identicon, err := identicon.GenerateBase64(pk)
if err != nil {

View File

@ -2106,7 +2106,8 @@ func (s *MessengerSuite) TestRequestHistoricMessagesRequest() {
m := s.newMessenger(shh)
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond)
defer cancel()
cursor, err := m.RequestHistoricMessages(ctx, nil, 10, 20, []byte{0x01})
m.mailserver = []byte("mailserver-id")
cursor, err := m.RequestHistoricMessages(ctx, 10, 20, []byte{0x01})
s.EqualError(err, ctx.Err().Error())
s.Empty(cursor)
// verify request is correct

View File

@ -0,0 +1,3 @@
DROP TABLE push_notification_client_servers;
DROP TABLE push_notification_client_info;
DROP INDEX idx_push_notification_client_info_public_key;

View File

@ -0,0 +1,15 @@
CREATE TABLE IF NOT EXISTS push_notification_client_servers (
public_key BLOB NOT NULL,
registered BOOLEAN DEFAULT FALSE,
registered_at INT NOT NULL DEFAULT 0,
UNIQUE(public_key) ON CONFLICT REPLACE
);
CREATE TABLE IF NOT EXISTS push_notification_client_info (
public_key BLOB NOT NULL,
installation_id TEXT NOT NULL,
access_token TEXT NOT NULL,
UNIQUE(public_key, installation_id) ON CONFLICT REPLACE
);
CREATE INDEX idx_push_notification_client_info_public_key ON push_notification_client_info(public_key, installation_id);

View File

@ -0,0 +1,9 @@
// This file is necessary because "github.com/status-im/migrate/v4"
// can't handle files starting with a prefix. At least that's the case
// for go-bindata.
// If go-bindata is called from the same directory, asset names
// have no prefix and "github.com/status-im/migrate/v4" works as expected.
package migrations
//go:generate go-bindata -pkg migrations -o ./migrations.go .

View File

@ -0,0 +1,57 @@
package push_notification_client
import (
"crypto/ecdsa"
"database/sql"
"github.com/status-im/status-go/eth-node/crypto"
)
type Persistence struct {
db *sql.DB
}
func NewPersistence(db *sql.DB) *Persistence {
return &Persistence{db: db}
}
func (p *Persistence) TrackPushNotification(messageID []byte) error {
return nil
}
func (p *Persistence) ShouldSentNotificationFor(publicKey *ecdsa.PublicKey, messageID []byte) (bool, error) {
return false, nil
}
func (p *Persistence) SentFor(publicKey *ecdsa.PublicKey, messageID []byte) error {
return nil
}
func (p *Persistence) UpsertServer(server *PushNotificationServer) error {
_, err := p.db.Exec(`INSERT INTO push_notification_client_servers (public_key, registered, registered_at) VALUES (?,?,?)`, crypto.CompressPubkey(server.publicKey), server.registered, server.registeredAt)
return err
}
func (p *Persistence) GetServers() ([]*PushNotificationServer, error) {
rows, err := p.db.Query(`SELECT public_key, registered, registered_at FROM push_notification_client_servers`)
if err != nil {
return nil, err
}
var servers []*PushNotificationServer
for rows.Next() {
server := &PushNotificationServer{}
var key []byte
err := rows.Scan(&key, &server.registered, &server.registeredAt)
if err != nil {
return nil, err
}
parsedKey, err := crypto.DecompressPubkey(key)
if err != nil {
return nil, err
}
server.publicKey = parsedKey
servers = append(servers, server)
}
return servers, nil
}

View File

@ -0,0 +1,71 @@
package push_notification_client
import (
"io/ioutil"
"os"
"testing"
"github.com/stretchr/testify/suite"
"github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/protocol/common"
"github.com/status-im/status-go/protocol/sqlite"
)
func TestSQLitePersistenceSuite(t *testing.T) {
suite.Run(t, new(SQLitePersistenceSuite))
}
type SQLitePersistenceSuite struct {
suite.Suite
tmpFile *os.File
persistence *Persistence
}
func (s *SQLitePersistenceSuite) SetupTest() {
tmpFile, err := ioutil.TempFile("", "")
s.Require().NoError(err)
s.tmpFile = tmpFile
database, err := sqlite.Open(s.tmpFile.Name(), "")
s.Require().NoError(err)
s.persistence = NewPersistence(database)
}
func (s *SQLitePersistenceSuite) TearDownTest() {
_ = os.Remove(s.tmpFile.Name())
}
func (s *SQLitePersistenceSuite) TestSaveAndRetrieveServer() {
key, err := crypto.GenerateKey()
s.Require().NoError(err)
server := &PushNotificationServer{
publicKey: &key.PublicKey,
registered: true,
registeredAt: 1,
}
s.Require().NoError(s.persistence.UpsertServer(server))
retrievedServers, err := s.persistence.GetServers()
s.Require().NoError(err)
s.Require().Len(retrievedServers, 1)
s.Require().True(retrievedServers[0].registered)
s.Require().Equal(int64(1), retrievedServers[0].registeredAt)
s.Require().True(common.IsPubKeyEqual(retrievedServers[0].publicKey, &key.PublicKey))
server.registered = false
server.registeredAt = 2
s.Require().NoError(s.persistence.UpsertServer(server))
retrievedServers, err = s.persistence.GetServers()
s.Require().NoError(err)
s.Require().Len(retrievedServers, 1)
s.Require().False(retrievedServers[0].registered)
s.Require().Equal(int64(2), retrievedServers[0].registeredAt)
s.Require().True(common.IsPubKeyEqual(retrievedServers[0].publicKey, &key.PublicKey))
}

View File

@ -20,8 +20,15 @@ import (
const accessTokenKeyLength = 16
type PushNotificationServer struct {
key *ecdsa.PublicKey
registered bool
publicKey *ecdsa.PublicKey
registered bool
registeredAt int64
}
type PushNotificationInfo struct {
AccessToken string
InstallationID string
PublicKey *ecdsa.PublicKey
}
type Config struct {
@ -35,10 +42,6 @@ type Config struct {
// AllowOnlyFromContacts indicates whether we should be receiving push notifications
// only from contacts
AllowOnlyFromContacts bool
// ContactIDs is the public keys for each contact that we allow notifications from
ContactIDs []*ecdsa.PublicKey
// MutedChatIDs is the IDs of the chats we don't want to receive notifications from
MutedChatIDs []string
// PushNotificationServers is an array of push notification servers we want to register with
PushNotificationServers []*PushNotificationServer
// InstallationID is the installation-id for this device
@ -122,10 +125,10 @@ func (p *Client) NotifyOnMessageID(messageID []byte) error {
return nil
}
func (p *Client) mutedChatIDsHashes() [][]byte {
func (p *Client) mutedChatIDsHashes(chatIDs []string) [][]byte {
var mutedChatListHashes [][]byte
for _, chatID := range p.config.MutedChatIDs {
for _, chatID := range chatIDs {
mutedChatListHashes = append(mutedChatListHashes, shake256(chatID))
}
@ -148,9 +151,9 @@ func (p *Client) encryptToken(publicKey *ecdsa.PublicKey, token []byte) ([]byte,
return encryptedToken, nil
}
func (p *Client) allowedUserList(token []byte) ([][]byte, error) {
func (p *Client) allowedUserList(token []byte, contactIDs []*ecdsa.PublicKey) ([][]byte, error) {
var encryptedTokens [][]byte
for _, publicKey := range p.config.ContactIDs {
for _, publicKey := range contactIDs {
encryptedToken, err := p.encryptToken(publicKey, token)
if err != nil {
return nil, err
@ -162,9 +165,9 @@ func (p *Client) allowedUserList(token []byte) ([][]byte, error) {
return encryptedTokens, nil
}
func (p *Client) buildPushNotificationRegistrationMessage() (*protobuf.PushNotificationRegistration, error) {
func (p *Client) buildPushNotificationRegistrationMessage(contactIDs []*ecdsa.PublicKey, mutedChatIDs []string) (*protobuf.PushNotificationRegistration, error) {
token := uuid.New().String()
allowedUserList, err := p.allowedUserList([]byte(token))
allowedUserList, err := p.allowedUserList([]byte(token), contactIDs)
if err != nil {
return nil, err
}
@ -175,13 +178,20 @@ func (p *Client) buildPushNotificationRegistrationMessage() (*protobuf.PushNotif
InstallationId: p.config.InstallationID,
Token: p.DeviceToken,
Enabled: p.config.RemoteNotificationsEnabled,
BlockedChatList: p.mutedChatIDsHashes(),
BlockedChatList: p.mutedChatIDsHashes(mutedChatIDs),
AllowedUserList: allowedUserList,
}
return options, nil
}
func (p *Client) Register(deviceToken string) error {
func (p *Client) Register(deviceToken string, contactIDs []*ecdsa.PublicKey, mutedChatIDs []string) error {
servers, err := p.persistence.GetServers()
if err != nil {
return err
}
if len(servers) == 0 {
return errors.New("no servers to register with")
}
return nil
}
@ -205,16 +215,40 @@ func (p *Client) HandlePushNotificationResponse(ack *protobuf.PushNotificationRe
return nil
}
func (p *Client) SetContactIDs(contactIDs []*ecdsa.PublicKey) error {
p.config.ContactIDs = contactIDs
// Update or schedule update
return nil
func (c *Client) AddPushNotificationServer(publicKey *ecdsa.PublicKey) error {
currentServers, err := c.persistence.GetServers()
if err != nil {
return err
}
for _, server := range currentServers {
if common.IsPubKeyEqual(server.publicKey, publicKey) {
return errors.New("push notification server already added")
}
}
return c.persistence.UpsertServer(&PushNotificationServer{
publicKey: publicKey,
})
}
func (p *Client) SetMutedChatIDs(chatIDs []string) error {
p.config.MutedChatIDs = chatIDs
// Update or schedule update
return nil
func (c *Client) RetrievePushNotificationInfo(publicKey *ecdsa.PublicKey) ([]*PushNotificationInfo, error) {
return nil, nil
/*
currentServers, err := c.persistence.GetServers()
if err != nil {
return err
}
for _, server := range currentServers {
if common.IsPubKeyEqual(server.publicKey, publicKey) {
return errors.New("push notification server already added")
}
}
return c.persistence.UpsertServer(&PushNotificationServer{
publicKey: publicKey,
})*/
}
func encryptAccessToken(plaintext []byte, key []byte, reader io.Reader) ([]byte, error) {

View File

@ -1,26 +0,0 @@
package push_notification_client
import (
"crypto/ecdsa"
"database/sql"
)
type Persistence struct {
db *sql.DB
}
func NewPersistence(db *sql.DB) *Persistence {
return &Persistence{db: db}
}
func (p *Persistence) TrackPushNotification(messageID []byte) error {
return nil
}
func (p *Persistence) ShouldSentNotificationFor(publicKey *ecdsa.PublicKey, messageID []byte) (bool, error) {
return false, nil
}
func (p *Persistence) SentFor(publicKey *ecdsa.PublicKey, messageID []byte) error {
return nil
}

View File

@ -56,8 +56,6 @@ func TestBuildPushNotificationRegisterMessage(t *testing.T) {
config := &Config{
Identity: identity,
RemoteNotificationsEnabled: true,
MutedChatIDs: mutedChatList,
ContactIDs: contactIDs,
InstallationID: myInstallationID,
}
@ -77,7 +75,7 @@ func TestBuildPushNotificationRegisterMessage(t *testing.T) {
AllowedUserList: [][]byte{encryptedToken},
}
actualMessage, err := client.buildPushNotificationRegistrationMessage()
actualMessage, err := client.buildPushNotificationRegistrationMessage(contactIDs, mutedChatList)
require.NoError(t, err)
require.Equal(t, options, actualMessage)

View File

@ -7,7 +7,6 @@ var ErrEmptyPushNotificationRegistrationPayload = errors.New("empty payload")
var ErrMalformedPushNotificationRegistrationInstallationID = errors.New("invalid installationID")
var ErrEmptyPushNotificationRegistrationPublicKey = errors.New("no public key")
var ErrCouldNotUnmarshalPushNotificationRegistration = errors.New("could not unmarshal preferences")
var ErrInvalidCiphertextLength = errors.New("invalid cyphertext length")
var ErrMalformedPushNotificationRegistrationDeviceToken = errors.New("invalid device token")
var ErrMalformedPushNotificationRegistrationAccessToken = errors.New("invalid access token")
var ErrUnknownPushNotificationRegistrationTokenType = errors.New("invalid token type")

View File

@ -1,6 +1,7 @@
package push_notification_server
import (
"context"
"crypto/ecdsa"
"errors"
@ -14,7 +15,6 @@ import (
)
const encryptedPayloadKeyLength = 16
const nonceLength = 12
type Config struct {
// Identity is our identity key
@ -57,7 +57,7 @@ func (p *Server) decryptRegistration(publicKey *ecdsa.PublicKey, payload []byte)
return nil, err
}
return decrypt(payload, sharedKey)
return common.Decrypt(payload, sharedKey)
}
// ValidateRegistration validates a new message against the last one received for a given installationID and and public key
@ -90,7 +90,7 @@ func (p *Server) ValidateRegistration(publicKey *ecdsa.PublicKey, payload []byte
return nil, ErrMalformedPushNotificationRegistrationInstallationID
}
previousRegistration, err := p.persistence.GetPushNotificationRegistrationByPublicKeyAndInstallationID(hashPublicKey(publicKey), registration.InstallationId)
previousRegistration, err := p.persistence.GetPushNotificationRegistrationByPublicKeyAndInstallationID(common.HashPublicKey(publicKey), registration.InstallationId)
if err != nil {
return nil, err
}
@ -205,7 +205,7 @@ func (p *Server) HandlePushNotificationRequest(request *protobuf.PushNotificatio
func (p *Server) HandlePushNotificationRegistration(publicKey *ecdsa.PublicKey, payload []byte) *protobuf.PushNotificationRegistrationResponse {
response := &protobuf.PushNotificationRegistrationResponse{
RequestId: shake256(payload),
RequestId: common.Shake256(payload),
}
registration, err := p.ValidateRegistration(publicKey, payload)
@ -227,12 +227,12 @@ func (p *Server) HandlePushNotificationRegistration(publicKey *ecdsa.PublicKey,
Version: registration.Version,
InstallationId: registration.InstallationId,
}
if err := p.persistence.SavePushNotificationRegistration(hashPublicKey(publicKey), emptyRegistration); err != nil {
if err := p.persistence.SavePushNotificationRegistration(common.HashPublicKey(publicKey), emptyRegistration); err != nil {
response.Error = protobuf.PushNotificationRegistrationResponse_INTERNAL_ERROR
return response
}
} else if err := p.persistence.SavePushNotificationRegistration(hashPublicKey(publicKey), registration); err != nil {
} else if err := p.persistence.SavePushNotificationRegistration(common.HashPublicKey(publicKey), registration); err != nil {
response.Error = protobuf.PushNotificationRegistrationResponse_INTERNAL_ERROR
return response
}
@ -243,17 +243,60 @@ func (p *Server) HandlePushNotificationRegistration(publicKey *ecdsa.PublicKey,
}
func (p *Server) HandlePushNotificationRegistration2(publicKey *ecdsa.PublicKey, payload []byte) error {
return nil
response := p.HandlePushNotificationRegistration(publicKey, payload)
if response == nil {
return nil
}
encodedMessage, err := proto.Marshal(response)
if err != nil {
return err
}
rawMessage := &common.RawMessage{
Payload: encodedMessage,
MessageType: protobuf.ApplicationMetadataMessage_PUSH_NOTIFICATION_REGISTRATION_RESPONSE,
}
_, err = p.messageProcessor.SendPrivate(context.Background(), publicKey, rawMessage)
return err
}
func (p *Server) HandlePushNotificationQuery2(publicKey *ecdsa.PublicKey, query protobuf.PushNotificationQuery) error {
return nil
response := p.HandlePushNotificationQuery(&query)
if response == nil {
return nil
}
encodedMessage, err := proto.Marshal(response)
if err != nil {
return err
}
rawMessage := &common.RawMessage{
Payload: encodedMessage,
MessageType: protobuf.ApplicationMetadataMessage_PUSH_NOTIFICATION_QUERY_RESPONSE,
}
_, err = p.messageProcessor.SendPrivate(context.Background(), publicKey, rawMessage)
return err
}
func (p *Server) HandlePushNotificationRequest2(publicKey *ecdsa.PublicKey,
request protobuf.PushNotificationRequest) error {
return nil
response := p.HandlePushNotificationRequest(&request)
if response == nil {
return nil
}
encodedMessage, err := proto.Marshal(response)
if err != nil {
return err
}
rawMessage := &common.RawMessage{
Payload: encodedMessage,
MessageType: protobuf.ApplicationMetadataMessage_PUSH_NOTIFICATION_RESPONSE,
}
_, err = p.messageProcessor.SendPrivate(context.Background(), publicKey, rawMessage)
return err
}

View File

@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/suite"
"github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/protocol/common"
"github.com/status-im/status-go/protocol/protobuf"
"github.com/status-im/status-go/protocol/sqlite"
)
@ -49,9 +50,9 @@ func (s *SQLitePersistenceSuite) TestSaveAndRetrieve() {
Version: 5,
}
s.Require().NoError(s.persistence.SavePushNotificationRegistration(hashPublicKey(&key.PublicKey), registration))
s.Require().NoError(s.persistence.SavePushNotificationRegistration(common.HashPublicKey(&key.PublicKey), registration))
retrievedRegistration, err := s.persistence.GetPushNotificationRegistrationByPublicKeyAndInstallationID(hashPublicKey(&key.PublicKey), installationID)
retrievedRegistration, err := s.persistence.GetPushNotificationRegistrationByPublicKeyAndInstallationID(common.HashPublicKey(&key.PublicKey), installationID)
s.Require().NoError(err)
s.Require().True(proto.Equal(registration, retrievedRegistration))

View File

@ -11,6 +11,7 @@ import (
"github.com/golang/protobuf/proto"
"github.com/stretchr/testify/suite"
"github.com/status-im/status-go/protocol/common"
"github.com/status-im/status-go/protocol/protobuf"
"github.com/status-im/status-go/protocol/sqlite"
)
@ -76,24 +77,24 @@ func (s *ServerSuite) TestPushNotificationServerValidateRegistration() {
// Invalid cyphertext length
_, err = s.server.ValidateRegistration(&s.key.PublicKey, []byte("too short"))
s.Require().Equal(ErrInvalidCiphertextLength, err)
s.Require().Equal(common.ErrInvalidCiphertextLength, err)
// Invalid cyphertext length
_, err = s.server.ValidateRegistration(&s.key.PublicKey, []byte("too short"))
s.Require().Equal(ErrInvalidCiphertextLength, err)
s.Require().Equal(common.ErrInvalidCiphertextLength, err)
// Invalid ciphertext
_, err = s.server.ValidateRegistration(&s.key.PublicKey, []byte("not too short but invalid"))
s.Require().Error(ErrInvalidCiphertextLength, err)
s.Require().Error(common.ErrInvalidCiphertextLength, err)
// Different key ciphertext
cyphertext, err := encrypt([]byte("plaintext"), make([]byte, 32), rand.Reader)
cyphertext, err := common.Encrypt([]byte("plaintext"), make([]byte, 32), rand.Reader)
s.Require().NoError(err)
_, err = s.server.ValidateRegistration(&s.key.PublicKey, cyphertext)
s.Require().Error(err)
// Right cyphertext but non unmarshable payload
cyphertext, err = encrypt([]byte("plaintext"), s.sharedKey, rand.Reader)
cyphertext, err = common.Encrypt([]byte("plaintext"), s.sharedKey, rand.Reader)
s.Require().NoError(err)
_, err = s.server.ValidateRegistration(&s.key.PublicKey, cyphertext)
s.Require().Equal(ErrCouldNotUnmarshalPushNotificationRegistration, err)
@ -106,7 +107,7 @@ func (s *ServerSuite) TestPushNotificationServerValidateRegistration() {
})
s.Require().NoError(err)
cyphertext, err = encrypt(payload, s.sharedKey, rand.Reader)
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
s.Require().NoError(err)
_, err = s.server.ValidateRegistration(&s.key.PublicKey, cyphertext)
s.Require().Equal(ErrMalformedPushNotificationRegistrationInstallationID, err)
@ -118,7 +119,7 @@ func (s *ServerSuite) TestPushNotificationServerValidateRegistration() {
InstallationId: "abc",
Version: 1,
})
cyphertext, err = encrypt(payload, s.sharedKey, rand.Reader)
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
s.Require().NoError(err)
_, err = s.server.ValidateRegistration(&s.key.PublicKey, cyphertext)
s.Require().Equal(ErrMalformedPushNotificationRegistrationInstallationID, err)
@ -131,7 +132,7 @@ func (s *ServerSuite) TestPushNotificationServerValidateRegistration() {
})
s.Require().NoError(err)
cyphertext, err = encrypt(payload, s.sharedKey, rand.Reader)
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
s.Require().NoError(err)
_, err = s.server.ValidateRegistration(&s.key.PublicKey, cyphertext)
s.Require().Equal(ErrInvalidPushNotificationRegistrationVersion, err)
@ -145,11 +146,11 @@ func (s *ServerSuite) TestPushNotificationServerValidateRegistration() {
})
s.Require().NoError(err)
cyphertext, err = encrypt(payload, s.sharedKey, rand.Reader)
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
s.Require().NoError(err)
// Setup persistence
s.Require().NoError(s.persistence.SavePushNotificationRegistration(hashPublicKey(&s.key.PublicKey), &protobuf.PushNotificationRegistration{
s.Require().NoError(s.persistence.SavePushNotificationRegistration(common.HashPublicKey(&s.key.PublicKey), &protobuf.PushNotificationRegistration{
AccessToken: s.accessToken,
TokenType: protobuf.PushNotificationRegistration_APN_TOKEN,
InstallationId: s.installationID,
@ -159,7 +160,7 @@ func (s *ServerSuite) TestPushNotificationServerValidateRegistration() {
s.Require().Equal(ErrInvalidPushNotificationRegistrationVersion, err)
// Cleanup persistence
s.Require().NoError(s.persistence.DeletePushNotificationRegistration(hashPublicKey(&s.key.PublicKey), s.installationID))
s.Require().NoError(s.persistence.DeletePushNotificationRegistration(common.HashPublicKey(&s.key.PublicKey), s.installationID))
// Unregistering message
payload, err = proto.Marshal(&protobuf.PushNotificationRegistration{
@ -170,7 +171,7 @@ func (s *ServerSuite) TestPushNotificationServerValidateRegistration() {
})
s.Require().NoError(err)
cyphertext, err = encrypt(payload, s.sharedKey, rand.Reader)
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
s.Require().NoError(err)
_, err = s.server.ValidateRegistration(&s.key.PublicKey, cyphertext)
s.Require().Nil(err)
@ -183,7 +184,7 @@ func (s *ServerSuite) TestPushNotificationServerValidateRegistration() {
})
s.Require().NoError(err)
cyphertext, err = encrypt(payload, s.sharedKey, rand.Reader)
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
s.Require().NoError(err)
_, err = s.server.ValidateRegistration(&s.key.PublicKey, cyphertext)
s.Require().Equal(ErrMalformedPushNotificationRegistrationAccessToken, err)
@ -197,7 +198,7 @@ func (s *ServerSuite) TestPushNotificationServerValidateRegistration() {
})
s.Require().NoError(err)
cyphertext, err = encrypt(payload, s.sharedKey, rand.Reader)
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
s.Require().NoError(err)
_, err = s.server.ValidateRegistration(&s.key.PublicKey, cyphertext)
s.Require().Equal(ErrMalformedPushNotificationRegistrationAccessToken, err)
@ -211,7 +212,7 @@ func (s *ServerSuite) TestPushNotificationServerValidateRegistration() {
})
s.Require().NoError(err)
cyphertext, err = encrypt(payload, s.sharedKey, rand.Reader)
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
s.Require().NoError(err)
_, err = s.server.ValidateRegistration(&s.key.PublicKey, cyphertext)
s.Require().Equal(ErrMalformedPushNotificationRegistrationDeviceToken, err)
@ -225,7 +226,7 @@ func (s *ServerSuite) TestPushNotificationServerValidateRegistration() {
})
s.Require().NoError(err)
cyphertext, err = encrypt(payload, s.sharedKey, rand.Reader)
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
s.Require().NoError(err)
_, err = s.server.ValidateRegistration(&s.key.PublicKey, cyphertext)
s.Require().Equal(ErrUnknownPushNotificationRegistrationTokenType, err)
@ -240,7 +241,7 @@ func (s *ServerSuite) TestPushNotificationServerValidateRegistration() {
})
s.Require().NoError(err)
cyphertext, err = encrypt(payload, s.sharedKey, rand.Reader)
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
s.Require().NoError(err)
_, err = s.server.ValidateRegistration(&s.key.PublicKey, cyphertext)
s.Require().NoError(err)
@ -278,7 +279,7 @@ func (s *ServerSuite) TestPushNotificationHandleRegistration() {
s.Require().Equal(response.Error, protobuf.PushNotificationRegistrationResponse_MALFORMED_MESSAGE)
// Different key ciphertext
cyphertext, err := encrypt([]byte("plaintext"), make([]byte, 32), rand.Reader)
cyphertext, err := common.Encrypt([]byte("plaintext"), make([]byte, 32), rand.Reader)
s.Require().NoError(err)
response = s.server.HandlePushNotificationRegistration(&s.key.PublicKey, cyphertext)
s.Require().NotNil(response)
@ -286,7 +287,7 @@ func (s *ServerSuite) TestPushNotificationHandleRegistration() {
s.Require().Equal(response.Error, protobuf.PushNotificationRegistrationResponse_MALFORMED_MESSAGE)
// Right cyphertext but non unmarshable payload
cyphertext, err = encrypt([]byte("plaintext"), s.sharedKey, rand.Reader)
cyphertext, err = common.Encrypt([]byte("plaintext"), s.sharedKey, rand.Reader)
s.Require().NoError(err)
response = s.server.HandlePushNotificationRegistration(&s.key.PublicKey, cyphertext)
s.Require().NotNil(response)
@ -300,7 +301,7 @@ func (s *ServerSuite) TestPushNotificationHandleRegistration() {
})
s.Require().NoError(err)
cyphertext, err = encrypt(payload, s.sharedKey, rand.Reader)
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
s.Require().NoError(err)
response = s.server.HandlePushNotificationRegistration(&s.key.PublicKey, cyphertext)
s.Require().NotNil(response)
@ -313,7 +314,7 @@ func (s *ServerSuite) TestPushNotificationHandleRegistration() {
InstallationId: "abc",
Version: 1,
})
cyphertext, err = encrypt(payload, s.sharedKey, rand.Reader)
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
s.Require().NoError(err)
response = s.server.HandlePushNotificationRegistration(&s.key.PublicKey, cyphertext)
s.Require().NotNil(response)
@ -327,7 +328,7 @@ func (s *ServerSuite) TestPushNotificationHandleRegistration() {
})
s.Require().NoError(err)
cyphertext, err = encrypt(payload, s.sharedKey, rand.Reader)
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
s.Require().NoError(err)
response = s.server.HandlePushNotificationRegistration(&s.key.PublicKey, cyphertext)
s.Require().NotNil(response)
@ -342,11 +343,11 @@ func (s *ServerSuite) TestPushNotificationHandleRegistration() {
})
s.Require().NoError(err)
cyphertext, err = encrypt(payload, s.sharedKey, rand.Reader)
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
s.Require().NoError(err)
// Setup persistence
s.Require().NoError(s.persistence.SavePushNotificationRegistration(hashPublicKey(&s.key.PublicKey), &protobuf.PushNotificationRegistration{
s.Require().NoError(s.persistence.SavePushNotificationRegistration(common.HashPublicKey(&s.key.PublicKey), &protobuf.PushNotificationRegistration{
AccessToken: s.accessToken,
InstallationId: s.installationID,
Version: 2}))
@ -357,7 +358,7 @@ func (s *ServerSuite) TestPushNotificationHandleRegistration() {
s.Require().Equal(response.Error, protobuf.PushNotificationRegistrationResponse_VERSION_MISMATCH)
// Cleanup persistence
s.Require().NoError(s.persistence.DeletePushNotificationRegistration(hashPublicKey(&s.key.PublicKey), s.installationID))
s.Require().NoError(s.persistence.DeletePushNotificationRegistration(common.HashPublicKey(&s.key.PublicKey), s.installationID))
// Missing access token
payload, err = proto.Marshal(&protobuf.PushNotificationRegistration{
@ -366,7 +367,7 @@ func (s *ServerSuite) TestPushNotificationHandleRegistration() {
})
s.Require().NoError(err)
cyphertext, err = encrypt(payload, s.sharedKey, rand.Reader)
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
s.Require().NoError(err)
response = s.server.HandlePushNotificationRegistration(&s.key.PublicKey, cyphertext)
s.Require().NotNil(response)
@ -381,7 +382,7 @@ func (s *ServerSuite) TestPushNotificationHandleRegistration() {
})
s.Require().NoError(err)
cyphertext, err = encrypt(payload, s.sharedKey, rand.Reader)
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
s.Require().NoError(err)
response = s.server.HandlePushNotificationRegistration(&s.key.PublicKey, cyphertext)
s.Require().NotNil(response)
@ -396,7 +397,7 @@ func (s *ServerSuite) TestPushNotificationHandleRegistration() {
})
s.Require().NoError(err)
cyphertext, err = encrypt(payload, s.sharedKey, rand.Reader)
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
s.Require().NoError(err)
response = s.server.HandlePushNotificationRegistration(&s.key.PublicKey, cyphertext)
s.Require().NotNil(response)
@ -414,14 +415,14 @@ func (s *ServerSuite) TestPushNotificationHandleRegistration() {
payload, err = proto.Marshal(registration)
s.Require().NoError(err)
cyphertext, err = encrypt(payload, s.sharedKey, rand.Reader)
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
s.Require().NoError(err)
response = s.server.HandlePushNotificationRegistration(&s.key.PublicKey, cyphertext)
s.Require().NotNil(response)
s.Require().True(response.Success)
// Pull from the db
retrievedRegistration, err := s.persistence.GetPushNotificationRegistrationByPublicKeyAndInstallationID(hashPublicKey(&s.key.PublicKey), s.installationID)
retrievedRegistration, err := s.persistence.GetPushNotificationRegistrationByPublicKeyAndInstallationID(common.HashPublicKey(&s.key.PublicKey), s.installationID)
s.Require().NoError(err)
s.Require().NotNil(retrievedRegistration)
s.Require().True(proto.Equal(retrievedRegistration, registration))
@ -435,25 +436,25 @@ func (s *ServerSuite) TestPushNotificationHandleRegistration() {
})
s.Require().NoError(err)
cyphertext, err = encrypt(payload, s.sharedKey, rand.Reader)
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
s.Require().NoError(err)
response = s.server.HandlePushNotificationRegistration(&s.key.PublicKey, cyphertext)
s.Require().NotNil(response)
s.Require().True(response.Success)
// Check is gone from the db
retrievedRegistration, err = s.persistence.GetPushNotificationRegistrationByPublicKeyAndInstallationID(hashPublicKey(&s.key.PublicKey), s.installationID)
retrievedRegistration, err = s.persistence.GetPushNotificationRegistrationByPublicKeyAndInstallationID(common.HashPublicKey(&s.key.PublicKey), s.installationID)
s.Require().NoError(err)
s.Require().NotNil(retrievedRegistration)
s.Require().Empty(retrievedRegistration.AccessToken)
s.Require().Empty(retrievedRegistration.Token)
s.Require().Equal(uint64(2), retrievedRegistration.Version)
s.Require().Equal(s.installationID, retrievedRegistration.InstallationId)
s.Require().Equal(shake256(cyphertext), response.RequestId)
s.Require().Equal(common.Shake256(cyphertext), response.RequestId)
}
func (s *ServerSuite) TestHandlePushNotificationQueryNoFiltering() {
hashedPublicKey := hashPublicKey(&s.key.PublicKey)
hashedPublicKey := common.HashPublicKey(&s.key.PublicKey)
// Successful
registration := &protobuf.PushNotificationRegistration{
Token: "abc",
@ -465,7 +466,7 @@ func (s *ServerSuite) TestHandlePushNotificationQueryNoFiltering() {
payload, err := proto.Marshal(registration)
s.Require().NoError(err)
cyphertext, err := encrypt(payload, s.sharedKey, rand.Reader)
cyphertext, err := common.Encrypt(payload, s.sharedKey, rand.Reader)
s.Require().NoError(err)
response := s.server.HandlePushNotificationRegistration(&s.key.PublicKey, cyphertext)
s.Require().NotNil(response)
@ -486,7 +487,7 @@ func (s *ServerSuite) TestHandlePushNotificationQueryNoFiltering() {
}
func (s *ServerSuite) TestHandlePushNotificationQueryWithFiltering() {
hashedPublicKey := hashPublicKey(&s.key.PublicKey)
hashedPublicKey := common.HashPublicKey(&s.key.PublicKey)
allowedUserList := [][]byte{[]byte("a")}
// Successful
@ -501,7 +502,7 @@ func (s *ServerSuite) TestHandlePushNotificationQueryWithFiltering() {
payload, err := proto.Marshal(registration)
s.Require().NoError(err)
cyphertext, err := encrypt(payload, s.sharedKey, rand.Reader)
cyphertext, err := common.Encrypt(payload, s.sharedKey, rand.Reader)
s.Require().NoError(err)
response := s.server.HandlePushNotificationRegistration(&s.key.PublicKey, cyphertext)
s.Require().NotNil(response)

View File

@ -0,0 +1,145 @@
package protocol
import (
"context"
"crypto/ecdsa"
"io/ioutil"
"os"
"testing"
"github.com/google/uuid"
"github.com/stretchr/testify/suite"
"go.uber.org/zap"
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/tt"
"github.com/status-im/status-go/whisper/v6"
)
func TestMessengerPushNotificationSuite(t *testing.T) {
suite.Run(t, new(MessengerPushNotificationSuite))
}
type MessengerPushNotificationSuite struct {
suite.Suite
m *Messenger // main instance of Messenger
privateKey *ecdsa.PrivateKey // private key for the main instance of Messenger
// If one wants to send messages between different instances of Messenger,
// a single Whisper service should be shared.
shh types.Whisper
tmpFiles []*os.File // files to clean up
logger *zap.Logger
}
func (s *MessengerPushNotificationSuite) SetupTest() {
s.logger = tt.MustCreateTestLogger()
config := whisper.DefaultConfig
config.MinimumAcceptedPOW = 0
shh := whisper.New(&config)
s.shh = gethbridge.NewGethWhisperWrapper(shh)
s.Require().NoError(shh.Start(nil))
s.m = s.newMessenger(s.shh)
s.privateKey = s.m.identity
}
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(),
}
m, err := NewMessenger(
privateKey,
&testNode{shh: shh},
uuid.New().String(),
options...,
)
s.Require().NoError(err)
err = m.Init()
s.Require().NoError(err)
s.tmpFiles = append(s.tmpFiles, tmpFile)
return m
}
func (s *MessengerPushNotificationSuite) newMessenger(shh types.Whisper) *Messenger {
privateKey, err := crypto.GenerateKey()
s.Require().NoError(err)
return s.newMessengerWithKey(s.shh, privateKey)
}
func (s *MessengerPushNotificationSuite) TestReceivePushNotification() {
deviceToken := "token"
server := s.newMessenger(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)
s.Require().NoError(err)
info, err := client2.pushNotificationClient.RetrievePushNotificationInfo(&s.m.identity.PublicKey)
s.Require().NoError(err)
s.Require().NotNil(info)
/*
s.Require().Len(response.Contacts, 1)
contact := response.Contacts[0]
s.Require().True(contact.IsAdded())
s.Require().Len(response.Chats, 1)
chat := response.Chats[0]
s.Require().False(chat.Active, "It does not create an active chat")
// Wait for the message to reach its destination
response, err = WaitOnMessengerResponse(
s.m,
func(r *MessengerResponse) bool { return len(r.Contacts) > 0 },
"contact request not received",
)
s.Require().NoError(err)
receivedContact := response.Contacts[0]
s.Require().Equal(theirName, receivedContact.Name)
s.Require().Equal(theirPicture, receivedContact.Photo)
s.Require().False(receivedContact.ENSVerified)
s.Require().True(receivedContact.HasBeenAdded())
s.Require().NotEmpty(receivedContact.LastUpdated)
newPicture := "new-picture"
err = theirMessenger.SendPushNotifications(context.Background(), newName, newPicture)
s.Require().NoError(err)
// Wait for the message to reach its destination
response, err = WaitOnMessengerResponse(
s.m,
func(r *MessengerResponse) bool {
return len(r.Contacts) > 0 && response.Contacts[0].ID == theirContactID
},
"contact request not received",
)
s.Require().NoError(err)
receivedContact = response.Contacts[0]
s.Require().Equal(theirContactID, receivedContact.ID)
s.Require().Equal(newName, receivedContact.Name)
s.Require().Equal(newPicture, receivedContact.Photo)
s.Require().False(receivedContact.ENSVerified)
s.Require().True(receivedContact.HasBeenAdded())
s.Require().NotEmpty(receivedContact.LastUpdated)
*/
}

View File

@ -7,6 +7,7 @@ import (
encryptmigrations "github.com/status-im/status-go/protocol/encryption/migrations"
appmigrations "github.com/status-im/status-go/protocol/migrations"
push_notification_client_migrations "github.com/status-im/status-go/protocol/push_notification_client/migrations"
push_notification_server_migrations "github.com/status-im/status-go/protocol/push_notification_server/migrations"
wakumigrations "github.com/status-im/status-go/protocol/transport/waku/migrations"
whispermigrations "github.com/status-im/status-go/protocol/transport/whisper/migrations"
@ -40,6 +41,10 @@ var defaultMigrations = []migrationsWithGetter{
Names: push_notification_server_migrations.AssetNames(),
Getter: push_notification_server_migrations.Asset,
},
{
Names: push_notification_client_migrations.AssetNames(),
Getter: push_notification_client_migrations.Asset,
},
}
func prepareMigrations(migrations []migrationsWithGetter) ([]string, getter, error) {

View File

@ -339,6 +339,9 @@ func (s *Service) DisableInstallation(installationID string) error {
// UpdateMailservers updates information about selected mail servers.
func (s *Service) UpdateMailservers(nodes []*enode.Node) error {
if len(nodes) > 0 && s.messenger != nil {
s.messenger.SetMailserver(nodes[0].ID().Bytes())
}
if err := s.peerStore.Update(nodes); err != nil {
return err
}