Use local chat-id for matching messages

This commit is contained in:
Andrea Maria Piana 2020-07-28 09:53:32 +02:00
parent d067b56fc2
commit 29f25c5486
No known key found for this signature in database
GPG Key ID: AA6CCA6DE0E06424
11 changed files with 195 additions and 107 deletions

View File

@ -129,7 +129,7 @@ func (s *MessageProcessorSuite) TestHandleDecodedMessagesWrapped() {
s.Require().Equal(1, len(decodedMessages))
s.Require().Equal(&authorKey.PublicKey, decodedMessages[0].SigPubKey())
s.Require().Equal(v1protocol.MessageID(&authorKey.PublicKey, wrappedPayload), decodedMessages[0].ID)
parsedMessage := decodedMessages[0].ParsedMessage.(protobuf.ChatMessage)
parsedMessage := decodedMessages[0].ParsedMessage.Interface().(protobuf.ChatMessage)
s.Require().Equal(encodedPayload, decodedMessages[0].DecryptedPayload)
s.Require().True(proto.Equal(&s.testMessage, &parsedMessage))
s.Require().Equal(protobuf.ApplicationMetadataMessage_CHAT_MESSAGE, decodedMessages[0].Type)
@ -167,7 +167,7 @@ func (s *MessageProcessorSuite) TestHandleDecodedMessagesDatasync() {
s.Require().Equal(&authorKey.PublicKey, decodedMessages[0].SigPubKey())
s.Require().Equal(v1protocol.MessageID(&authorKey.PublicKey, wrappedPayload), decodedMessages[0].ID)
s.Require().Equal(encodedPayload, decodedMessages[0].DecryptedPayload)
parsedMessage := decodedMessages[0].ParsedMessage.(protobuf.ChatMessage)
parsedMessage := decodedMessages[0].ParsedMessage.Interface().(protobuf.ChatMessage)
s.Require().True(proto.Equal(&s.testMessage, &parsedMessage))
s.Require().Equal(protobuf.ApplicationMetadataMessage_CHAT_MESSAGE, decodedMessages[0].Type)
}
@ -235,7 +235,7 @@ func (s *MessageProcessorSuite) TestHandleDecodedMessagesDatasyncEncrypted() {
s.Require().Equal(&authorKey.PublicKey, decodedMessages[0].SigPubKey())
s.Require().Equal(v1protocol.MessageID(&authorKey.PublicKey, wrappedPayload), decodedMessages[0].ID)
s.Require().Equal(encodedPayload, decodedMessages[0].DecryptedPayload)
parsedMessage := decodedMessages[0].ParsedMessage.(protobuf.ChatMessage)
parsedMessage := decodedMessages[0].ParsedMessage.Interface().(protobuf.ChatMessage)
s.Require().True(proto.Equal(&s.testMessage, &parsedMessage))
s.Require().Equal(protobuf.ApplicationMetadataMessage_CHAT_MESSAGE, decodedMessages[0].Type)
}

View File

@ -22,11 +22,14 @@ type EmojiReaction struct {
// SigPubKey is the ecdsa encoded public key of the emoji reaction author
SigPubKey *ecdsa.PublicKey `json:"-"`
// LocalChatID is the chatID of the local chat (one-to-one are not symmetric)
LocalChatID string `json:"localChatId"`
}
// ID is the Keccak256() contatenation of From-ChatID-MessageID-EmojiType
// ID is the Keccak256() contatenation of From-MessageID-EmojiType
func (e EmojiReaction) ID() string {
return types.EncodeHex(crypto.Keccak256([]byte(fmt.Sprintf("%s%s%s%d", e.From, e.ChatId, e.MessageId, e.Type))))
return types.EncodeHex(crypto.Keccak256([]byte(fmt.Sprintf("%s%s%d", e.From, e.MessageId, e.Type))))
}
// GetSigPubKey returns an ecdsa encoded public key

View File

@ -670,10 +670,20 @@ func (m *MessageHandler) messageExists(messageID string, existingMessagesMap map
func (m *MessageHandler) HandleEmojiReaction(state *ReceivedMessageState, pbEmojiR protobuf.EmojiReaction) error {
logger := m.logger.With(zap.String("site", "HandleEmojiReaction"))
if err := ValidateReceivedEmojiReaction(&pbEmojiR, state.Timesource.GetCurrentTime()); err != nil {
logger.Error("invalid emoji reaction", zap.Error(err))
return err
}
from := state.CurrentMessageState.Contact.ID
// check that the user can actually send an emoji for this message
existingEmoji, err := m.persistence.EmojiReactionByFromMessageIDAndType(from, pbEmojiR.MessageId, pbEmojiR.Type)
emojiReaction := &EmojiReaction{
EmojiReaction: pbEmojiR,
From: from,
SigPubKey: state.CurrentMessageState.PublicKey,
}
existingEmoji, err := m.persistence.EmojiReactionByID(emojiReaction.ID())
if err != errRecordNotFound && err != nil {
return err
}
@ -683,18 +693,15 @@ func (m *MessageHandler) HandleEmojiReaction(state *ReceivedMessageState, pbEmoj
return nil
}
emojiReaction := &EmojiReaction{
EmojiReaction: pbEmojiR,
From: from,
SigPubKey: state.CurrentMessageState.PublicKey,
}
chat, err := m.matchChatEntity(emojiReaction, state.AllChats, state.Timesource)
if err != nil {
return err // matchChatEntity returns a descriptive error message
}
// TODO: make sure the user can actualy send an emoji for this chat.
logger.Info("Handling emoji reaction")
// Set local chat id
emojiReaction.LocalChatID = chat.ID
logger.Debug("Handling emoji reaction")
if chat.LastClockValue < pbEmojiR.Clock {
chat.LastClockValue = pbEmojiR.Clock

View File

@ -408,6 +408,7 @@ func (db sqlitePersistence) MessagesByIDs(ids []string) ([]*Message, error) {
return nil, err
}
defer rows.Close()
var result []*Message
for rows.Next() {
var message Message
@ -496,14 +497,14 @@ func (db sqlitePersistence) EmojiReactionsByChatID(chatID string, currCursor str
if currCursor != "" {
cursorWhere = "AND substr('0000000000000000000000000000000000000000000000000000000000000000' || m.clock_value, -64, 64) || m.id <= ?"
}
args := []interface{}{chatID}
args := []interface{}{chatID, chatID}
if currCursor != "" {
args = append(args, currCursor)
}
args = append(args, limit)
// Build a new column `cursor` at the query time by having a fixed-sized clock value at the beginning
// concatenated with message ID. Results are sorted using this new column.
// This new column values can also be returned as a cursor for subsequent requests.
// NOTE: We match against local_chat_id for security reasons.
// As a user could potentially send an emoji reaction for a one to
// one/group chat that has no access to.
query := fmt.Sprintf(`
SELECT
e.clock_value,
@ -511,11 +512,14 @@ func (db sqlitePersistence) EmojiReactionsByChatID(chatID string, currCursor str
e.emoji_id,
e.message_id,
e.chat_id,
e.local_chat_id,
e.retracted
FROM
emoji_reactions e
WHERE NOT(e.retracted)
AND
e.local_chat_id = ?
AND
e.message_id IN
(SELECT id FROM user_messages m WHERE NOT(m.hide) AND m.local_chat_id = ? %s
ORDER BY substr('0000000000000000000000000000000000000000000000000000000000000000' || m.clock_value, -64, 64) || m.id DESC LIMIT ?)
@ -539,6 +543,7 @@ func (db sqlitePersistence) EmojiReactionsByChatID(chatID string, currCursor str
&emojiReaction.Type,
&emojiReaction.MessageId,
&emojiReaction.ChatId,
&emojiReaction.LocalChatID,
&emojiReaction.Retracted)
if err != nil {
return nil, err
@ -768,7 +773,7 @@ func (db sqlitePersistence) BlockContact(contact *Contact) ([]*Chat, error) {
}
func (db sqlitePersistence) SaveEmojiReaction(emojiReaction *EmojiReaction) (err error) {
query := "INSERT INTO emoji_reactions(id,clock_value,source,emoji_id,message_id,chat_id,retracted) VALUES (?,?,?,?,?,?,?)"
query := "INSERT INTO emoji_reactions(id,clock_value,source,emoji_id,message_id,chat_id,local_chat_id,retracted) VALUES (?,?,?,?,?,?,?,?)"
stmt, err := db.db.Prepare(query)
if err != nil {
return
@ -781,6 +786,7 @@ func (db sqlitePersistence) SaveEmojiReaction(emojiReaction *EmojiReaction) (err
emojiReaction.Type,
emojiReaction.MessageId,
emojiReaction.ChatId,
emojiReaction.LocalChatID,
emojiReaction.Retracted,
)
@ -788,26 +794,14 @@ func (db sqlitePersistence) SaveEmojiReaction(emojiReaction *EmojiReaction) (err
}
func (db sqlitePersistence) EmojiReactionByID(id string) (*EmojiReaction, error) {
tx, err := db.db.BeginTx(context.Background(), &sql.TxOptions{})
if err != nil {
return nil, err
}
defer func() {
if err == nil {
err = tx.Commit()
return
}
// don't shadow original error
_ = tx.Rollback()
}()
row := tx.QueryRow(
row := db.db.QueryRow(
`SELECT
clock_value,
source,
emoji_id,
message_id,
chat_id,
local_chat_id,
retracted
FROM
emoji_reactions
@ -816,71 +810,15 @@ func (db sqlitePersistence) EmojiReactionByID(id string) (*EmojiReaction, error)
`, id)
emojiReaction := new(EmojiReaction)
args := []interface{}{
&emojiReaction.Clock,
err := row.Scan(&emojiReaction.Clock,
&emojiReaction.From,
&emojiReaction.Type,
&emojiReaction.MessageId,
&emojiReaction.ChatId,
&emojiReaction.LocalChatID,
&emojiReaction.Retracted,
}
err = row.Scan(args...)
switch err {
case sql.ErrNoRows:
return nil, errRecordNotFound
case nil:
return emojiReaction, nil
default:
return nil, err
}
}
func (db sqlitePersistence) EmojiReactionByFromMessageIDAndType(from string, messageID string, emojiType protobuf.EmojiReaction_Type) (*EmojiReaction, error) {
tx, err := db.db.BeginTx(context.Background(), &sql.TxOptions{})
if err != nil {
return nil, err
}
defer func() {
if err == nil {
err = tx.Commit()
return
}
// don't shadow original error
_ = tx.Rollback()
}()
row := tx.QueryRow(
`SELECT
clock_value,
source,
emoji_id,
message_id,
chat_id,
retracted
FROM
emoji_reactions
WHERE
emoji_reactions.source = ?
AND
emoji_reactions.message_id = ?
AND
emoji_reactions.emoji_id = ?
`,
from,
messageID,
emojiType,
)
emojiReaction := new(EmojiReaction)
err = row.Scan(&emojiReaction.Clock,
&emojiReaction.From,
&emojiReaction.Type,
&emojiReaction.MessageId,
&emojiReaction.ChatId,
&emojiReaction.Retracted)
switch err {
case sql.ErrNoRows:
return nil, errRecordNotFound
@ -890,8 +828,3 @@ func (db sqlitePersistence) EmojiReactionByFromMessageIDAndType(from string, mes
return nil, err
}
}
func (db sqlitePersistence) RetractEmojiReaction(id string) error {
_, err := db.db.Exec(`UPDATE emoji_reactions SET retracted = 1 WHERE id = ?`, id)
return err
}

View File

@ -225,3 +225,27 @@ func ValidateReceivedChatMessage(message *protobuf.ChatMessage, whisperTimestamp
return nil
}
func ValidateReceivedEmojiReaction(emoji *protobuf.EmojiReaction, whisperTimestamp uint64) error {
if err := validateClockValue(emoji.Clock, whisperTimestamp); err != nil {
return err
}
if len(emoji.MessageId) == 0 {
return errors.New("message-id can't be empty")
}
if len(emoji.ChatId) == 0 {
return errors.New("chat-id can't be empty")
}
if emoji.Type == protobuf.EmojiReaction_UNKNOWN_EMOJI_REACTION_TYPE {
return errors.New("unknown emoji reaction type")
}
if emoji.MessageType == protobuf.MessageType_UNKNOWN_MESSAGE_TYPE {
return errors.New("unknown message type")
}
return nil
}

View File

@ -477,3 +477,105 @@ func (s *MessageValidatorSuite) TestValidatePlainTextMessage() {
})
}
}
func (s *MessageValidatorSuite) TestValidateEmojiReaction() {
testCases := []struct {
Name string
Valid bool
WhisperTimestamp uint64
Message protobuf.EmojiReaction
}{
{
Name: "valid emoji reaction",
Valid: true,
WhisperTimestamp: 30,
Message: protobuf.EmojiReaction{
Clock: 30,
ChatId: "chat-id",
MessageId: "message-id",
MessageType: protobuf.MessageType_ONE_TO_ONE,
Type: protobuf.EmojiReaction_LOVE,
},
},
{
Name: "valid emoji retraction",
Valid: true,
WhisperTimestamp: 30,
Message: protobuf.EmojiReaction{
Clock: 30,
ChatId: "0.34",
MessageId: "message-id",
Type: protobuf.EmojiReaction_LOVE,
MessageType: protobuf.MessageType_ONE_TO_ONE,
Retracted: true,
},
},
{
Name: "missing chatID",
Valid: false,
WhisperTimestamp: 30,
Message: protobuf.EmojiReaction{
Clock: 30,
MessageId: "message-id",
MessageType: protobuf.MessageType_ONE_TO_ONE,
Type: protobuf.EmojiReaction_LOVE,
},
},
{
Name: "missing messageID",
Valid: false,
WhisperTimestamp: 30,
Message: protobuf.EmojiReaction{
Clock: 30,
ChatId: "chat-id",
MessageType: protobuf.MessageType_ONE_TO_ONE,
Type: protobuf.EmojiReaction_LOVE,
},
},
{
Name: "missing type",
Valid: false,
WhisperTimestamp: 30,
Message: protobuf.EmojiReaction{
Clock: 30,
ChatId: "chat-id",
MessageId: "message-id",
MessageType: protobuf.MessageType_ONE_TO_ONE,
},
},
{
Name: "missing message type",
Valid: false,
WhisperTimestamp: 30,
Message: protobuf.EmojiReaction{
Clock: 30,
ChatId: "chat-id",
MessageId: "message-id",
Type: protobuf.EmojiReaction_LOVE,
},
},
{
Name: "clock value too high",
Valid: false,
WhisperTimestamp: 30,
Message: protobuf.EmojiReaction{
Clock: 900000,
ChatId: "chat-id",
MessageId: "message-id",
MessageType: protobuf.MessageType_ONE_TO_ONE,
Type: protobuf.EmojiReaction_LOVE,
},
},
}
for _, tc := range testCases {
s.Run(tc.Name, func() {
err := ValidateReceivedEmojiReaction(&tc.Message, tc.WhisperTimestamp)
if tc.Valid {
s.Nil(err)
} else {
s.NotNil(err)
}
})
}
}

View File

@ -3245,7 +3245,8 @@ func (m *Messenger) SendEmojiReaction(ctx context.Context, chatID, messageID str
ChatId: chatID,
Type: protobuf.EmojiReaction_Type(emojiID),
},
From: types.EncodeHex(crypto.FromECDSAPub(&m.identity.PublicKey)),
LocalChatID: chatID,
From: types.EncodeHex(crypto.FromECDSAPub(&m.identity.PublicKey)),
}
encodedMessage, err := m.encodeChatEntity(chat, emojiR)
if err != nil {
@ -3334,7 +3335,7 @@ func (m *Messenger) SendEmojiReactionRetraction(ctx context.Context, emojiReacti
response.Chats = []*Chat{chat}
// Persist retraction state for emoji reaction
err = m.persistence.RetractEmojiReaction(emojiReactionID)
err = m.persistence.SaveEmojiReaction(emojiR)
if err != nil {
return nil, err
}

View File

@ -17,7 +17,7 @@
// 1595862781_add_audio_data.down.sql (0)
// 1595862781_add_audio_data.up.sql (246B)
// 1595865249_create_emoji_reactions_table.down.sql (27B)
// 1595865249_create_emoji_reactions_table.up.sql (265B)
// 1595865249_create_emoji_reactions_table.up.sql (300B)
// doc.go (850B)
package migrations
@ -427,7 +427,7 @@ func _1595865249_create_emoji_reactions_tableDownSql() (*asset, error) {
return a, nil
}
var __1595865249_create_emoji_reactions_tableUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x74\xce\xbf\x4e\xc3\x30\x10\xc7\xf1\x3d\x52\xde\xe1\x37\x82\xc4\xc0\xce\x64\xcc\x45\x58\x18\xa7\x72\xaf\xa8\x9d\x22\xcb\x3d\x81\xa1\xc5\x92\xed\xf0\xfc\x88\x64\xe1\x8f\x98\x3f\x77\xdf\x3b\xed\x49\x31\x81\xd5\xad\x25\x98\x01\x6e\x64\xd0\xde\x6c\x79\x0b\x39\xe7\xd7\x34\x15\x09\xb1\xa5\xfc\x5e\x71\xd1\x77\x40\x3a\xe2\x49\x79\x7d\xaf\x3c\x36\xde\x3c\x2a\x7f\xc0\x03\x1d\x30\x3a\xe8\xd1\x0d\xd6\x68\x86\xa7\x8d\x55\x9a\xae\xbe\xc6\xe3\x29\xc7\xb7\xe9\x23\x9c\x66\x81\x71\xbc\xe4\xdd\xce\xda\x05\x6b\x9e\x4b\x14\x30\xed\x7f\xc1\x7a\x39\x1d\xff\xae\x9c\xa5\xd6\xf0\x2c\xd3\xb7\x37\x7e\x78\x7c\x09\xed\x5f\x2c\xd2\x4a\x88\x4d\xd6\xee\x1d\x0d\x6a\x67\x19\xd7\x7d\x77\x79\xd3\x77\x9f\x01\x00\x00\xff\xff\xf2\xdf\x03\x4e\x09\x01\x00\x00")
var __1595865249_create_emoji_reactions_tableUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x7c\xce\xbd\x4e\x03\x31\x10\x04\xe0\xfe\xa4\x7b\x87\x29\x41\xa2\xa0\xa7\x32\x66\x4f\x58\x18\x5f\xe4\x6c\x50\x52\x9d\x2c\x67\x05\x07\x0e\x96\x6c\x87\xe7\x47\xe4\x1a\x7e\x44\xea\x6f\x76\x67\xb4\x27\xc5\x04\x56\xb7\x96\x60\x06\xb8\x91\x41\x5b\xb3\xe6\x35\xe4\x90\x5f\xe7\xa9\x48\x88\x6d\xce\xef\x15\x17\x7d\x07\xcc\x7b\x3c\x29\xaf\xef\x95\xc7\xca\x9b\x47\xe5\x77\x78\xa0\x1d\x46\x07\x3d\xba\xc1\x1a\xcd\xf0\xb4\xb2\x4a\xd3\xd5\x57\x3c\xa6\x1c\xdf\xa6\x8f\x90\x8e\x02\xe3\xf8\xf4\xde\x6d\xac\x3d\x61\xcd\xc7\x12\x05\x4c\xdb\x5f\xb0\x34\xcf\xfb\xbf\x27\x07\xa9\x35\x3c\xcb\xf4\x6d\xc6\x0f\x8f\x2f\xa1\xfd\x8b\x29\xc7\x90\xa6\xb3\x91\x22\xad\x84\xd8\x64\xa9\xbe\xa3\x41\x6d\x2c\xe3\xba\xef\x2e\x6f\xfa\xee\x33\x00\x00\xff\xff\xe4\x28\x05\xe0\x2c\x01\x00\x00")
func _1595865249_create_emoji_reactions_tableUpSqlBytes() ([]byte, error) {
return bindataRead(
@ -442,8 +442,8 @@ func _1595865249_create_emoji_reactions_tableUpSql() (*asset, error) {
return nil, err
}
info := bindataFileInfo{name: "1595865249_create_emoji_reactions_table.up.sql", size: 265, mode: os.FileMode(0644), modTime: time.Unix(1595865239, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x13, 0x28, 0xa4, 0x70, 0xf0, 0xfe, 0xd, 0xc2, 0x16, 0xc3, 0x1d, 0xbb, 0x3c, 0x1, 0xf6, 0x58, 0x69, 0x2a, 0x27, 0x21, 0xbc, 0x8b, 0xc8, 0x1a, 0xd, 0x36, 0x2d, 0x29, 0x0, 0xc3, 0xfa, 0xad}}
info := bindataFileInfo{name: "1595865249_create_emoji_reactions_table.up.sql", size: 300, mode: os.FileMode(0644), modTime: time.Unix(1595921491, 0)}
a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x3e, 0xc5, 0x43, 0x5c, 0x3d, 0x53, 0x43, 0x2c, 0x1a, 0xa5, 0xb6, 0xbf, 0x7, 0x4, 0x5a, 0x3e, 0x40, 0x8b, 0xa4, 0x57, 0x12, 0x58, 0xbc, 0x42, 0xe2, 0xc3, 0xde, 0x76, 0x98, 0x80, 0xe2, 0xbe}}
return a, nil
}

View File

@ -5,5 +5,6 @@ CREATE TABLE IF NOT EXISTS emoji_reactions (
emoji_id INT NOT NULL,
message_id VARCHAR NOT NULL,
chat_id VARCHAR NOT NULL,
local_chat_id VARCHAR NOT NULL,
retracted INT DEFAULT 0
);

View File

@ -407,7 +407,8 @@ func TestPersistenceEmojiReactions(t *testing.T) {
ChatId: chatID,
Type: protobuf.EmojiReaction_SAD,
},
From: from1,
LocalChatID: chatID,
From: from1,
}))
// Insert retracted emoji reaction
@ -419,7 +420,8 @@ func TestPersistenceEmojiReactions(t *testing.T) {
Type: protobuf.EmojiReaction_SAD,
Retracted: true,
},
From: from2,
LocalChatID: chatID,
From: from2,
}))
// Insert retracted emoji reaction out of pagination
@ -430,7 +432,8 @@ func TestPersistenceEmojiReactions(t *testing.T) {
ChatId: chatID,
Type: protobuf.EmojiReaction_SAD,
},
From: from2,
LocalChatID: chatID,
From: from2,
}))
// Insert retracted emoji reaction out of pagination
@ -441,7 +444,20 @@ func TestPersistenceEmojiReactions(t *testing.T) {
ChatId: chatID,
Type: protobuf.EmojiReaction_SAD,
},
From: from3,
LocalChatID: chatID,
From: from3,
}))
// Wrong local chat id
require.NoError(t, p.SaveEmojiReaction(&EmojiReaction{
EmojiReaction: protobuf.EmojiReaction{
Clock: 1,
MessageId: id1,
ChatId: chatID,
Type: protobuf.EmojiReaction_LOVE,
},
LocalChatID: "wrong-chat-id",
From: from3,
}))
reactions, err := p.EmojiReactionsByChatID(chatID, "", 1)

View File

@ -54,7 +54,8 @@ func TestSignMembershipUpdate(t *testing.T) {
require.NoError(t, err)
// Encode message
encodedMessage := testMembershipUpdateMessageStruct.ToProtobuf()
encodedMessage, err := testMembershipUpdateMessageStruct.ToProtobuf()
require.NoError(t, err)
// Verify it
verifiedMessage, err := MembershipUpdateMessageFromProtobuf(encodedMessage)
require.NoError(t, err)