929 lines
31 KiB
Go
929 lines
31 KiB
Go
package pushnotificationserver
|
|
|
|
import (
|
|
"crypto/ecdsa"
|
|
"crypto/rand"
|
|
"testing"
|
|
|
|
"github.com/golang/protobuf/proto"
|
|
"github.com/stretchr/testify/suite"
|
|
|
|
"github.com/status-im/status-go/appdatabase"
|
|
"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"
|
|
"github.com/status-im/status-go/protocol/tt"
|
|
"github.com/status-im/status-go/t/helpers"
|
|
)
|
|
|
|
func TestServerSuite(t *testing.T) {
|
|
s := new(ServerSuite)
|
|
s.accessToken = "b6ae4fde-bb65-11ea-b3de-0242ac130004"
|
|
s.installationID = "c6ae4fde-bb65-11ea-b3de-0242ac130004"
|
|
|
|
suite.Run(t, s)
|
|
}
|
|
|
|
type ServerSuite struct {
|
|
suite.Suite
|
|
persistence Persistence
|
|
accessToken string
|
|
installationID string
|
|
identity *ecdsa.PrivateKey
|
|
key *ecdsa.PrivateKey
|
|
sharedKey []byte
|
|
grant []byte
|
|
server *Server
|
|
}
|
|
|
|
func (s *ServerSuite) SetupTest() {
|
|
db, err := helpers.SetupTestMemorySQLDB(appdatabase.DbInitializer{})
|
|
s.Require().NoError(err)
|
|
err = sqlite.Migrate(db)
|
|
s.Require().NoError(err)
|
|
|
|
s.persistence = NewSQLitePersistence(db)
|
|
|
|
identity, err := crypto.GenerateKey()
|
|
s.Require().NoError(err)
|
|
s.identity = identity
|
|
|
|
key, err := crypto.GenerateKey()
|
|
s.Require().NoError(err)
|
|
s.key = key
|
|
|
|
config := &Config{
|
|
Identity: identity,
|
|
Logger: tt.MustCreateTestLogger(),
|
|
}
|
|
|
|
s.server = New(config, s.persistence, nil)
|
|
|
|
sharedKey, err := s.server.generateSharedKey(&s.key.PublicKey)
|
|
s.Require().NoError(err)
|
|
s.sharedKey = sharedKey
|
|
signatureMaterial := s.server.buildGrantSignatureMaterial(&s.key.PublicKey, &identity.PublicKey, s.accessToken)
|
|
grant, err := crypto.Sign(signatureMaterial, s.key)
|
|
s.Require().NoError(err)
|
|
|
|
s.grant = grant
|
|
|
|
}
|
|
|
|
func (s *ServerSuite) TestPushNotificationServerValidateRegistration() {
|
|
|
|
// Empty payload
|
|
_, err := s.server.validateRegistration(&s.key.PublicKey, nil)
|
|
s.Require().Equal(ErrEmptyPushNotificationRegistrationPayload, err)
|
|
|
|
// Empty key
|
|
_, err = s.server.validateRegistration(nil, []byte("payload"))
|
|
s.Require().Equal(ErrEmptyPushNotificationRegistrationPublicKey, err)
|
|
|
|
// Invalid cyphertext length
|
|
_, err = s.server.validateRegistration(&s.key.PublicKey, []byte("too short"))
|
|
s.Require().Equal(common.ErrInvalidCiphertextLength, err)
|
|
|
|
// Invalid cyphertext length
|
|
_, err = s.server.validateRegistration(&s.key.PublicKey, []byte("too short"))
|
|
s.Require().Equal(common.ErrInvalidCiphertextLength, err)
|
|
|
|
// Invalid ciphertext
|
|
_, err = s.server.validateRegistration(&s.key.PublicKey, []byte("not too short but invalid"))
|
|
s.Require().Error(common.ErrInvalidCiphertextLength, err)
|
|
|
|
// Different key ciphertext
|
|
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 = 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)
|
|
|
|
// Missing installationID
|
|
payload, err := proto.Marshal(&protobuf.PushNotificationRegistration{
|
|
AccessToken: s.accessToken,
|
|
Grant: s.grant,
|
|
TokenType: protobuf.PushNotificationRegistration_APN_TOKEN,
|
|
Version: 1,
|
|
})
|
|
s.Require().NoError(err)
|
|
|
|
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)
|
|
|
|
// Malformed installationID
|
|
payload, err = proto.Marshal(&protobuf.PushNotificationRegistration{
|
|
AccessToken: s.accessToken,
|
|
TokenType: protobuf.PushNotificationRegistration_APN_TOKEN,
|
|
Grant: s.grant,
|
|
InstallationId: "abc",
|
|
Version: 1,
|
|
})
|
|
s.Require().NoError(err)
|
|
|
|
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)
|
|
|
|
// Version set to 0
|
|
payload, err = proto.Marshal(&protobuf.PushNotificationRegistration{
|
|
AccessToken: s.accessToken,
|
|
TokenType: protobuf.PushNotificationRegistration_APN_TOKEN,
|
|
Grant: s.grant,
|
|
InstallationId: s.installationID,
|
|
})
|
|
s.Require().NoError(err)
|
|
|
|
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)
|
|
|
|
// Version lower than previous one
|
|
payload, err = proto.Marshal(&protobuf.PushNotificationRegistration{
|
|
AccessToken: s.accessToken,
|
|
Grant: s.grant,
|
|
TokenType: protobuf.PushNotificationRegistration_APN_TOKEN,
|
|
InstallationId: s.installationID,
|
|
Version: 1,
|
|
})
|
|
s.Require().NoError(err)
|
|
|
|
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
|
|
s.Require().NoError(err)
|
|
|
|
// Setup persistence
|
|
s.Require().NoError(s.persistence.SavePushNotificationRegistration(common.HashPublicKey(&s.key.PublicKey), &protobuf.PushNotificationRegistration{
|
|
AccessToken: s.accessToken,
|
|
Grant: s.grant,
|
|
TokenType: protobuf.PushNotificationRegistration_APN_TOKEN,
|
|
InstallationId: s.installationID,
|
|
Version: 2}))
|
|
|
|
_, err = s.server.validateRegistration(&s.key.PublicKey, cyphertext)
|
|
s.Require().Equal(ErrInvalidPushNotificationRegistrationVersion, err)
|
|
|
|
// Cleanup persistence
|
|
s.Require().NoError(s.persistence.DeletePushNotificationRegistration(common.HashPublicKey(&s.key.PublicKey), s.installationID))
|
|
|
|
// Unregistering message
|
|
payload, err = proto.Marshal(&protobuf.PushNotificationRegistration{
|
|
InstallationId: s.installationID,
|
|
Unregister: true,
|
|
Version: 1,
|
|
})
|
|
s.Require().NoError(err)
|
|
|
|
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)
|
|
|
|
// Missing access token
|
|
payload, err = proto.Marshal(&protobuf.PushNotificationRegistration{
|
|
InstallationId: s.installationID,
|
|
Grant: s.grant,
|
|
TokenType: protobuf.PushNotificationRegistration_APN_TOKEN,
|
|
Version: 1,
|
|
})
|
|
s.Require().NoError(err)
|
|
|
|
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)
|
|
|
|
// Invalid access token
|
|
payload, err = proto.Marshal(&protobuf.PushNotificationRegistration{
|
|
AccessToken: "bc",
|
|
TokenType: protobuf.PushNotificationRegistration_APN_TOKEN,
|
|
Grant: s.grant,
|
|
InstallationId: s.installationID,
|
|
Version: 1,
|
|
})
|
|
s.Require().NoError(err)
|
|
|
|
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)
|
|
|
|
// Missing device token
|
|
payload, err = proto.Marshal(&protobuf.PushNotificationRegistration{
|
|
AccessToken: s.accessToken,
|
|
TokenType: protobuf.PushNotificationRegistration_APN_TOKEN,
|
|
Grant: s.grant,
|
|
InstallationId: s.installationID,
|
|
Version: 1,
|
|
})
|
|
s.Require().NoError(err)
|
|
|
|
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)
|
|
|
|
// Missing grant
|
|
payload, err = proto.Marshal(&protobuf.PushNotificationRegistration{
|
|
AccessToken: s.accessToken,
|
|
DeviceToken: "device-token",
|
|
InstallationId: s.installationID,
|
|
Version: 1,
|
|
})
|
|
s.Require().NoError(err)
|
|
|
|
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
|
|
s.Require().NoError(err)
|
|
_, err = s.server.validateRegistration(&s.key.PublicKey, cyphertext)
|
|
s.Require().Equal(ErrMalformedPushNotificationRegistrationGrant, err)
|
|
|
|
// Invalid grant
|
|
payload, err = proto.Marshal(&protobuf.PushNotificationRegistration{
|
|
AccessToken: s.accessToken,
|
|
TokenType: protobuf.PushNotificationRegistration_APN_TOKEN,
|
|
DeviceToken: "device-token",
|
|
Grant: crypto.Keccak256([]byte("invalid")),
|
|
InstallationId: s.installationID,
|
|
Version: 1,
|
|
})
|
|
s.Require().NoError(err)
|
|
|
|
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
|
|
s.Require().NoError(err)
|
|
_, err = s.server.validateRegistration(&s.key.PublicKey, cyphertext)
|
|
s.Require().Equal(ErrMalformedPushNotificationRegistrationGrant, err)
|
|
|
|
// Missing token type
|
|
payload, err = proto.Marshal(&protobuf.PushNotificationRegistration{
|
|
AccessToken: s.accessToken,
|
|
DeviceToken: "device-token",
|
|
Grant: s.grant,
|
|
InstallationId: s.installationID,
|
|
Version: 1,
|
|
})
|
|
s.Require().NoError(err)
|
|
|
|
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)
|
|
|
|
// Successful
|
|
payload, err = proto.Marshal(&protobuf.PushNotificationRegistration{
|
|
DeviceToken: "abc",
|
|
AccessToken: s.accessToken,
|
|
Grant: s.grant,
|
|
TokenType: protobuf.PushNotificationRegistration_APN_TOKEN,
|
|
InstallationId: s.installationID,
|
|
Version: 1,
|
|
})
|
|
s.Require().NoError(err)
|
|
|
|
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)
|
|
}
|
|
|
|
func (s *ServerSuite) TestPushNotificationHandleRegistration() {
|
|
// Empty payload
|
|
response := s.server.buildPushNotificationRegistrationResponse(&s.key.PublicKey, nil)
|
|
s.Require().NotNil(response)
|
|
s.Require().False(response.Success)
|
|
s.Require().Equal(response.Error, protobuf.PushNotificationRegistrationResponse_MALFORMED_MESSAGE)
|
|
|
|
// Empty key
|
|
response = s.server.buildPushNotificationRegistrationResponse(nil, []byte("payload"))
|
|
s.Require().NotNil(response)
|
|
s.Require().False(response.Success)
|
|
s.Require().Equal(response.Error, protobuf.PushNotificationRegistrationResponse_MALFORMED_MESSAGE)
|
|
|
|
// Invalid cyphertext length
|
|
response = s.server.buildPushNotificationRegistrationResponse(&s.key.PublicKey, []byte("too short"))
|
|
s.Require().NotNil(response)
|
|
s.Require().False(response.Success)
|
|
s.Require().Equal(response.Error, protobuf.PushNotificationRegistrationResponse_MALFORMED_MESSAGE)
|
|
|
|
// Invalid cyphertext length
|
|
response = s.server.buildPushNotificationRegistrationResponse(&s.key.PublicKey, []byte("too short"))
|
|
s.Require().NotNil(response)
|
|
s.Require().False(response.Success)
|
|
s.Require().Equal(response.Error, protobuf.PushNotificationRegistrationResponse_MALFORMED_MESSAGE)
|
|
|
|
// Invalid ciphertext
|
|
response = s.server.buildPushNotificationRegistrationResponse(&s.key.PublicKey, []byte("not too short but invalid"))
|
|
s.Require().NotNil(response)
|
|
s.Require().False(response.Success)
|
|
s.Require().Equal(response.Error, protobuf.PushNotificationRegistrationResponse_MALFORMED_MESSAGE)
|
|
|
|
// Different key ciphertext
|
|
cyphertext, err := common.Encrypt([]byte("plaintext"), make([]byte, 32), rand.Reader)
|
|
s.Require().NoError(err)
|
|
response = s.server.buildPushNotificationRegistrationResponse(&s.key.PublicKey, cyphertext)
|
|
s.Require().NotNil(response)
|
|
s.Require().False(response.Success)
|
|
s.Require().Equal(response.Error, protobuf.PushNotificationRegistrationResponse_MALFORMED_MESSAGE)
|
|
|
|
// Right cyphertext but non unmarshable payload
|
|
cyphertext, err = common.Encrypt([]byte("plaintext"), s.sharedKey, rand.Reader)
|
|
s.Require().NoError(err)
|
|
response = s.server.buildPushNotificationRegistrationResponse(&s.key.PublicKey, cyphertext)
|
|
s.Require().NotNil(response)
|
|
s.Require().False(response.Success)
|
|
s.Require().Equal(response.Error, protobuf.PushNotificationRegistrationResponse_MALFORMED_MESSAGE)
|
|
|
|
// Missing installationID
|
|
payload, err := proto.Marshal(&protobuf.PushNotificationRegistration{
|
|
AccessToken: s.accessToken,
|
|
Grant: s.grant,
|
|
Version: 1,
|
|
})
|
|
s.Require().NoError(err)
|
|
|
|
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
|
|
s.Require().NoError(err)
|
|
response = s.server.buildPushNotificationRegistrationResponse(&s.key.PublicKey, cyphertext)
|
|
s.Require().NotNil(response)
|
|
s.Require().False(response.Success)
|
|
s.Require().Equal(response.Error, protobuf.PushNotificationRegistrationResponse_MALFORMED_MESSAGE)
|
|
|
|
// Malformed installationID
|
|
payload, err = proto.Marshal(&protobuf.PushNotificationRegistration{
|
|
AccessToken: s.accessToken,
|
|
InstallationId: "abc",
|
|
Grant: s.grant,
|
|
Version: 1,
|
|
})
|
|
s.Require().NoError(err)
|
|
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
|
|
s.Require().NoError(err)
|
|
response = s.server.buildPushNotificationRegistrationResponse(&s.key.PublicKey, cyphertext)
|
|
s.Require().NotNil(response)
|
|
s.Require().False(response.Success)
|
|
s.Require().Equal(response.Error, protobuf.PushNotificationRegistrationResponse_MALFORMED_MESSAGE)
|
|
|
|
// Version set to 0
|
|
payload, err = proto.Marshal(&protobuf.PushNotificationRegistration{
|
|
AccessToken: s.accessToken,
|
|
Grant: s.grant,
|
|
InstallationId: s.installationID,
|
|
})
|
|
s.Require().NoError(err)
|
|
|
|
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
|
|
s.Require().NoError(err)
|
|
response = s.server.buildPushNotificationRegistrationResponse(&s.key.PublicKey, cyphertext)
|
|
s.Require().NotNil(response)
|
|
s.Require().False(response.Success)
|
|
s.Require().Equal(response.Error, protobuf.PushNotificationRegistrationResponse_VERSION_MISMATCH)
|
|
|
|
// Version lower than previous one
|
|
payload, err = proto.Marshal(&protobuf.PushNotificationRegistration{
|
|
AccessToken: s.accessToken,
|
|
Grant: s.grant,
|
|
InstallationId: s.installationID,
|
|
Version: 1,
|
|
})
|
|
s.Require().NoError(err)
|
|
|
|
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
|
|
s.Require().NoError(err)
|
|
|
|
// Setup persistence
|
|
s.Require().NoError(s.persistence.SavePushNotificationRegistration(common.HashPublicKey(&s.key.PublicKey), &protobuf.PushNotificationRegistration{
|
|
AccessToken: s.accessToken,
|
|
Grant: s.grant,
|
|
InstallationId: s.installationID,
|
|
Version: 2}))
|
|
|
|
response = s.server.buildPushNotificationRegistrationResponse(&s.key.PublicKey, cyphertext)
|
|
s.Require().NotNil(response)
|
|
s.Require().False(response.Success)
|
|
s.Require().Equal(response.Error, protobuf.PushNotificationRegistrationResponse_VERSION_MISMATCH)
|
|
|
|
// Cleanup persistence
|
|
s.Require().NoError(s.persistence.DeletePushNotificationRegistration(common.HashPublicKey(&s.key.PublicKey), s.installationID))
|
|
|
|
// Missing access token
|
|
payload, err = proto.Marshal(&protobuf.PushNotificationRegistration{
|
|
InstallationId: s.installationID,
|
|
Grant: s.grant,
|
|
Version: 1,
|
|
})
|
|
s.Require().NoError(err)
|
|
|
|
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
|
|
s.Require().NoError(err)
|
|
response = s.server.buildPushNotificationRegistrationResponse(&s.key.PublicKey, cyphertext)
|
|
s.Require().NotNil(response)
|
|
s.Require().False(response.Success)
|
|
s.Require().Equal(response.Error, protobuf.PushNotificationRegistrationResponse_MALFORMED_MESSAGE)
|
|
|
|
// Invalid access token
|
|
payload, err = proto.Marshal(&protobuf.PushNotificationRegistration{
|
|
AccessToken: "bc",
|
|
Grant: s.grant,
|
|
InstallationId: s.installationID,
|
|
Version: 1,
|
|
})
|
|
s.Require().NoError(err)
|
|
|
|
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
|
|
s.Require().NoError(err)
|
|
response = s.server.buildPushNotificationRegistrationResponse(&s.key.PublicKey, cyphertext)
|
|
s.Require().NotNil(response)
|
|
s.Require().False(response.Success)
|
|
s.Require().Equal(response.Error, protobuf.PushNotificationRegistrationResponse_MALFORMED_MESSAGE)
|
|
|
|
// Missing device token
|
|
payload, err = proto.Marshal(&protobuf.PushNotificationRegistration{
|
|
AccessToken: s.accessToken,
|
|
Grant: s.grant,
|
|
InstallationId: s.installationID,
|
|
Version: 1,
|
|
})
|
|
s.Require().NoError(err)
|
|
|
|
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
|
|
s.Require().NoError(err)
|
|
response = s.server.buildPushNotificationRegistrationResponse(&s.key.PublicKey, cyphertext)
|
|
s.Require().NotNil(response)
|
|
s.Require().False(response.Success)
|
|
s.Require().Equal(response.Error, protobuf.PushNotificationRegistrationResponse_MALFORMED_MESSAGE)
|
|
|
|
// Successful
|
|
registration := &protobuf.PushNotificationRegistration{
|
|
DeviceToken: "abc",
|
|
AccessToken: s.accessToken,
|
|
Grant: s.grant,
|
|
TokenType: protobuf.PushNotificationRegistration_APN_TOKEN,
|
|
InstallationId: s.installationID,
|
|
Version: 1,
|
|
}
|
|
payload, err = proto.Marshal(registration)
|
|
s.Require().NoError(err)
|
|
|
|
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
|
|
s.Require().NoError(err)
|
|
response = s.server.buildPushNotificationRegistrationResponse(&s.key.PublicKey, cyphertext)
|
|
s.Require().NotNil(response)
|
|
s.Require().True(response.Success)
|
|
|
|
// Pull from the db
|
|
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))
|
|
|
|
// Unregistering message
|
|
payload, err = proto.Marshal(&protobuf.PushNotificationRegistration{
|
|
DeviceToken: "token",
|
|
InstallationId: s.installationID,
|
|
Unregister: true,
|
|
Version: 2,
|
|
})
|
|
s.Require().NoError(err)
|
|
|
|
cyphertext, err = common.Encrypt(payload, s.sharedKey, rand.Reader)
|
|
s.Require().NoError(err)
|
|
response = s.server.buildPushNotificationRegistrationResponse(&s.key.PublicKey, cyphertext)
|
|
s.Require().NotNil(response)
|
|
s.Require().True(response.Success)
|
|
s.Require().Equal(common.Shake256(cyphertext), response.RequestId)
|
|
|
|
// Check is gone from the db
|
|
retrievedRegistration, err = s.persistence.GetPushNotificationRegistrationByPublicKeyAndInstallationID(common.HashPublicKey(&s.key.PublicKey), s.installationID)
|
|
s.Require().NoError(err)
|
|
s.Require().Nil(retrievedRegistration)
|
|
// Check version is maintained
|
|
version, err := s.persistence.GetPushNotificationRegistrationVersion(common.HashPublicKey(&s.key.PublicKey), s.installationID)
|
|
s.Require().NoError(err)
|
|
s.Require().Equal(uint64(2), version)
|
|
}
|
|
|
|
func (s *ServerSuite) TestbuildPushNotificationQueryResponseNoFiltering() {
|
|
hashedPublicKey := common.HashPublicKey(&s.key.PublicKey)
|
|
// Successful
|
|
registration := &protobuf.PushNotificationRegistration{
|
|
DeviceToken: "abc",
|
|
AccessToken: s.accessToken,
|
|
Grant: s.grant,
|
|
TokenType: protobuf.PushNotificationRegistration_APN_TOKEN,
|
|
InstallationId: s.installationID,
|
|
Version: 1,
|
|
}
|
|
payload, err := proto.Marshal(registration)
|
|
s.Require().NoError(err)
|
|
|
|
cyphertext, err := common.Encrypt(payload, s.sharedKey, rand.Reader)
|
|
s.Require().NoError(err)
|
|
response := s.server.buildPushNotificationRegistrationResponse(&s.key.PublicKey, cyphertext)
|
|
s.Require().NotNil(response)
|
|
s.Require().True(response.Success)
|
|
|
|
query := &protobuf.PushNotificationQuery{
|
|
PublicKeys: [][]byte{[]byte("non-existing"), hashedPublicKey},
|
|
}
|
|
|
|
queryResponse := s.server.buildPushNotificationQueryResponse(query)
|
|
s.Require().NotNil(queryResponse)
|
|
s.Require().True(queryResponse.Success)
|
|
s.Require().Len(queryResponse.Info, 1)
|
|
s.Require().Equal(s.accessToken, queryResponse.Info[0].AccessToken)
|
|
s.Require().Equal(hashedPublicKey, queryResponse.Info[0].PublicKey)
|
|
s.Require().Equal(s.installationID, queryResponse.Info[0].InstallationId)
|
|
s.Require().Nil(queryResponse.Info[0].AllowedKeyList)
|
|
}
|
|
|
|
func (s *ServerSuite) TestbuildPushNotificationQueryResponseWithFiltering() {
|
|
hashedPublicKey := common.HashPublicKey(&s.key.PublicKey)
|
|
allowedKeyList := [][]byte{[]byte("a")}
|
|
// Successful
|
|
|
|
registration := &protobuf.PushNotificationRegistration{
|
|
DeviceToken: "abc",
|
|
AccessToken: s.accessToken,
|
|
Grant: s.grant,
|
|
TokenType: protobuf.PushNotificationRegistration_APN_TOKEN,
|
|
InstallationId: s.installationID,
|
|
AllowFromContactsOnly: true,
|
|
AllowedKeyList: allowedKeyList,
|
|
Version: 1,
|
|
}
|
|
payload, err := proto.Marshal(registration)
|
|
s.Require().NoError(err)
|
|
|
|
cyphertext, err := common.Encrypt(payload, s.sharedKey, rand.Reader)
|
|
s.Require().NoError(err)
|
|
response := s.server.buildPushNotificationRegistrationResponse(&s.key.PublicKey, cyphertext)
|
|
s.Require().NotNil(response)
|
|
s.Require().True(response.Success)
|
|
|
|
query := &protobuf.PushNotificationQuery{
|
|
PublicKeys: [][]byte{[]byte("non-existing"), hashedPublicKey},
|
|
}
|
|
|
|
queryResponse := s.server.buildPushNotificationQueryResponse(query)
|
|
s.Require().NotNil(queryResponse)
|
|
s.Require().True(queryResponse.Success)
|
|
s.Require().Len(queryResponse.Info, 1)
|
|
s.Require().Equal(hashedPublicKey, queryResponse.Info[0].PublicKey)
|
|
s.Require().Equal(s.installationID, queryResponse.Info[0].InstallationId)
|
|
s.Require().Equal(allowedKeyList, queryResponse.Info[0].AllowedKeyList)
|
|
}
|
|
|
|
func (s *ServerSuite) TestPushNotificationMentions() {
|
|
existingChatID := []byte("existing-chat-id")
|
|
nonExistingChatID := []byte("non-existing-chat-id")
|
|
registration := &protobuf.PushNotificationRegistration{
|
|
DeviceToken: "abc",
|
|
AccessToken: s.accessToken,
|
|
Grant: s.grant,
|
|
TokenType: protobuf.PushNotificationRegistration_APN_TOKEN,
|
|
InstallationId: s.installationID,
|
|
AllowedMentionsChatList: [][]byte{existingChatID},
|
|
Version: 1,
|
|
}
|
|
payload, err := proto.Marshal(registration)
|
|
s.Require().NoError(err)
|
|
|
|
cyphertext, err := common.Encrypt(payload, s.sharedKey, rand.Reader)
|
|
s.Require().NoError(err)
|
|
response := s.server.buildPushNotificationRegistrationResponse(&s.key.PublicKey, cyphertext)
|
|
s.Require().NotNil(response)
|
|
s.Require().True(response.Success)
|
|
|
|
pushNotificationRequest := &protobuf.PushNotificationRequest{
|
|
MessageId: []byte("message-id"),
|
|
Requests: []*protobuf.PushNotification{
|
|
{
|
|
AccessToken: s.accessToken,
|
|
PublicKey: common.HashPublicKey(&s.key.PublicKey),
|
|
ChatId: existingChatID,
|
|
InstallationId: s.installationID,
|
|
Type: protobuf.PushNotification_MENTION,
|
|
},
|
|
{
|
|
AccessToken: s.accessToken,
|
|
PublicKey: common.HashPublicKey(&s.key.PublicKey),
|
|
ChatId: nonExistingChatID,
|
|
InstallationId: s.installationID,
|
|
Type: protobuf.PushNotification_MENTION,
|
|
},
|
|
},
|
|
}
|
|
|
|
pushNotificationResponse, requestAndRegistrations := s.server.buildPushNotificationRequestResponse(pushNotificationRequest)
|
|
s.Require().NotNil(pushNotificationResponse)
|
|
s.Require().NotNil(requestAndRegistrations)
|
|
|
|
// only one should succeed
|
|
s.Require().Len(requestAndRegistrations, 1)
|
|
}
|
|
|
|
func (s *ServerSuite) TestPushNotificationDisabledMentions() {
|
|
existingChatID := []byte("existing-chat-id")
|
|
registration := &protobuf.PushNotificationRegistration{
|
|
DeviceToken: "abc",
|
|
AccessToken: s.accessToken,
|
|
Grant: s.grant,
|
|
TokenType: protobuf.PushNotificationRegistration_APN_TOKEN,
|
|
BlockMentions: true,
|
|
InstallationId: s.installationID,
|
|
AllowedMentionsChatList: [][]byte{existingChatID},
|
|
Version: 1,
|
|
}
|
|
payload, err := proto.Marshal(registration)
|
|
s.Require().NoError(err)
|
|
|
|
cyphertext, err := common.Encrypt(payload, s.sharedKey, rand.Reader)
|
|
s.Require().NoError(err)
|
|
response := s.server.buildPushNotificationRegistrationResponse(&s.key.PublicKey, cyphertext)
|
|
s.Require().NotNil(response)
|
|
s.Require().True(response.Success)
|
|
|
|
pushNotificationRequest := &protobuf.PushNotificationRequest{
|
|
MessageId: []byte("message-id"),
|
|
Requests: []*protobuf.PushNotification{
|
|
{
|
|
AccessToken: s.accessToken,
|
|
PublicKey: common.HashPublicKey(&s.key.PublicKey),
|
|
ChatId: existingChatID,
|
|
InstallationId: s.installationID,
|
|
Type: protobuf.PushNotification_MENTION,
|
|
},
|
|
},
|
|
}
|
|
|
|
pushNotificationResponse, requestAndRegistrations := s.server.buildPushNotificationRequestResponse(pushNotificationRequest)
|
|
s.Require().NotNil(pushNotificationResponse)
|
|
s.Require().Nil(requestAndRegistrations)
|
|
}
|
|
|
|
func (s *ServerSuite) TestBuildPushNotificationReport() {
|
|
accessToken := "a"
|
|
chatID := []byte("chat-id")
|
|
author := []byte("author")
|
|
blockedAuthor := []byte("blocked-author")
|
|
blockedChatID := []byte("blocked-chat-id")
|
|
mutedChatID := []byte("muted-chat-id")
|
|
mutedChatList := [][]byte{mutedChatID, author}
|
|
blockedChatList := [][]byte{blockedChatID, blockedAuthor}
|
|
nonJoinedChatID := []byte("non-joined-chat-id")
|
|
allowedMentionsChatList := [][]byte{chatID}
|
|
validMessagePN := &protobuf.PushNotification{
|
|
Type: protobuf.PushNotification_MESSAGE,
|
|
ChatId: chatID,
|
|
Author: author,
|
|
AccessToken: accessToken,
|
|
}
|
|
validMentionPN := &protobuf.PushNotification{
|
|
Type: protobuf.PushNotification_MENTION,
|
|
ChatId: chatID,
|
|
AccessToken: accessToken,
|
|
}
|
|
validRegistration := &protobuf.PushNotificationRegistration{
|
|
AccessToken: accessToken,
|
|
BlockedChatList: blockedChatList,
|
|
AllowedMentionsChatList: allowedMentionsChatList,
|
|
MutedChatList: mutedChatList,
|
|
}
|
|
blockedMentionsRegistration := &protobuf.PushNotificationRegistration{
|
|
AccessToken: accessToken,
|
|
BlockMentions: true,
|
|
BlockedChatList: blockedChatList,
|
|
AllowedMentionsChatList: allowedMentionsChatList,
|
|
}
|
|
|
|
testCases := []struct {
|
|
name string
|
|
pn *protobuf.PushNotification
|
|
registration *protobuf.PushNotificationRegistration
|
|
expectedError error
|
|
expectedResponse *reportResult
|
|
}{
|
|
{
|
|
name: "valid message",
|
|
pn: validMessagePN,
|
|
registration: validRegistration,
|
|
expectedResponse: &reportResult{
|
|
sendNotification: true,
|
|
report: &protobuf.PushNotificationReport{
|
|
Success: true,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "valid mention",
|
|
pn: validMentionPN,
|
|
registration: validRegistration,
|
|
expectedResponse: &reportResult{
|
|
sendNotification: true,
|
|
report: &protobuf.PushNotificationReport{
|
|
Success: true,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "unknow push notification",
|
|
pn: &protobuf.PushNotification{
|
|
ChatId: chatID,
|
|
AccessToken: accessToken,
|
|
},
|
|
registration: validRegistration,
|
|
expectedError: errUnhandledPushNotificationType,
|
|
},
|
|
{
|
|
name: "empty registration",
|
|
pn: validMessagePN,
|
|
registration: nil,
|
|
expectedResponse: &reportResult{
|
|
sendNotification: false,
|
|
report: &protobuf.PushNotificationReport{
|
|
Success: false,
|
|
Error: protobuf.PushNotificationReport_NOT_REGISTERED,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "invalid access token message",
|
|
pn: &protobuf.PushNotification{
|
|
Type: protobuf.PushNotification_MESSAGE,
|
|
Author: author,
|
|
ChatId: chatID,
|
|
AccessToken: "invalid",
|
|
},
|
|
registration: validRegistration,
|
|
expectedResponse: &reportResult{
|
|
sendNotification: false,
|
|
report: &protobuf.PushNotificationReport{
|
|
Success: false,
|
|
Error: protobuf.PushNotificationReport_WRONG_TOKEN,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "invalid access token mention",
|
|
pn: &protobuf.PushNotification{
|
|
Type: protobuf.PushNotification_MENTION,
|
|
Author: author,
|
|
ChatId: chatID,
|
|
AccessToken: "invalid",
|
|
},
|
|
registration: validRegistration,
|
|
expectedResponse: &reportResult{
|
|
sendNotification: false,
|
|
report: &protobuf.PushNotificationReport{
|
|
Success: false,
|
|
Error: protobuf.PushNotificationReport_WRONG_TOKEN,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "blocked chat list message",
|
|
pn: &protobuf.PushNotification{
|
|
Type: protobuf.PushNotification_MESSAGE,
|
|
ChatId: blockedChatID,
|
|
Author: author,
|
|
AccessToken: accessToken,
|
|
},
|
|
registration: validRegistration,
|
|
expectedResponse: &reportResult{
|
|
sendNotification: false,
|
|
report: &protobuf.PushNotificationReport{
|
|
Success: true,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "muted chat list message",
|
|
pn: &protobuf.PushNotification{
|
|
Type: protobuf.PushNotification_MESSAGE,
|
|
ChatId: mutedChatID,
|
|
Author: author,
|
|
AccessToken: accessToken,
|
|
},
|
|
registration: validRegistration,
|
|
expectedResponse: &reportResult{
|
|
sendNotification: false,
|
|
report: &protobuf.PushNotificationReport{
|
|
Success: true,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "unmuted chat and user muted author",
|
|
pn: &protobuf.PushNotification{
|
|
Type: protobuf.PushNotification_MESSAGE,
|
|
ChatId: chatID,
|
|
Author: author,
|
|
AccessToken: accessToken,
|
|
},
|
|
registration: validRegistration,
|
|
expectedResponse: &reportResult{
|
|
sendNotification: true,
|
|
report: &protobuf.PushNotificationReport{
|
|
Success: true,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "muted chat list mention",
|
|
pn: &protobuf.PushNotification{
|
|
Type: protobuf.PushNotification_MENTION,
|
|
ChatId: mutedChatID,
|
|
Author: author,
|
|
AccessToken: accessToken,
|
|
},
|
|
registration: validRegistration,
|
|
expectedResponse: &reportResult{
|
|
sendNotification: false,
|
|
report: &protobuf.PushNotificationReport{
|
|
Success: true,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "blocked group chat message",
|
|
pn: &protobuf.PushNotification{
|
|
Type: protobuf.PushNotification_MESSAGE,
|
|
Author: blockedAuthor,
|
|
ChatId: chatID,
|
|
AccessToken: accessToken,
|
|
},
|
|
registration: validRegistration,
|
|
expectedResponse: &reportResult{
|
|
sendNotification: false,
|
|
report: &protobuf.PushNotificationReport{
|
|
Success: true,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "blocked chat list mention",
|
|
pn: &protobuf.PushNotification{
|
|
Type: protobuf.PushNotification_MENTION,
|
|
Author: blockedAuthor,
|
|
ChatId: chatID,
|
|
AccessToken: accessToken,
|
|
},
|
|
registration: validRegistration,
|
|
expectedResponse: &reportResult{
|
|
sendNotification: false,
|
|
report: &protobuf.PushNotificationReport{
|
|
Success: true,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "blocked mentions",
|
|
pn: &protobuf.PushNotification{
|
|
Type: protobuf.PushNotification_MENTION,
|
|
Author: author,
|
|
ChatId: chatID,
|
|
AccessToken: accessToken,
|
|
},
|
|
registration: blockedMentionsRegistration,
|
|
expectedResponse: &reportResult{
|
|
sendNotification: false,
|
|
report: &protobuf.PushNotificationReport{
|
|
Success: true,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "not in allowed mention chat list",
|
|
pn: &protobuf.PushNotification{
|
|
Type: protobuf.PushNotification_MENTION,
|
|
Author: author,
|
|
ChatId: nonJoinedChatID,
|
|
AccessToken: accessToken,
|
|
},
|
|
registration: validRegistration,
|
|
expectedResponse: &reportResult{
|
|
sendNotification: false,
|
|
report: &protobuf.PushNotificationReport{
|
|
Success: true,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
s.Run(tc.name, func() {
|
|
response, err := s.server.buildPushNotificationReport(tc.pn, tc.registration)
|
|
s.Require().Equal(tc.expectedError, err)
|
|
s.Require().Equal(tc.expectedResponse, response)
|
|
})
|
|
}
|
|
}
|