feat: make any member able to add new users to group chat

closes: #2823, #2825
This commit is contained in:
Patryk Osmaczko 2022-08-26 11:25:54 +02:00 committed by osmaczko
parent eee112738b
commit c01316524c
7 changed files with 307 additions and 1 deletions

View File

@ -255,3 +255,12 @@ func buildContact(publicKeyString string, publicKey *ecdsa.PublicKey) (*Contact,
func contactIDFromPublicKey(key *ecdsa.PublicKey) string {
return types.EncodeHex(crypto.FromECDSAPub(key))
}
func contactIDFromPublicKeyString(key string) (string, error) {
pubKey, err := common.HexToPubkey(key)
if err != nil {
return "", err
}
return contactIDFromPublicKey(pubKey), nil
}

View File

@ -379,6 +379,8 @@ func (s *MessengerEditMessageSuite) TestEditGroupChatMessage() {
err = s.m.SaveChat(ourChat)
s.NoError(err)
s.Require().NoError(makeMutualContact(s.m, &theirMessenger.identity.PublicKey))
members := []string{common.PubkeyToHex(&theirMessenger.identity.PublicKey)}
_, err = s.m.AddMembersToGroupChat(context.Background(), ourChat.ID, members)
s.NoError(err)

View File

@ -153,6 +153,8 @@ func (s *MessengerEmojiSuite) TestEmojiPrivateGroup() {
response, err := bob.CreateGroupChatWithMembers(context.Background(), "test", []string{})
s.NoError(err)
s.Require().NoError(makeMutualContact(bob, &alice.identity.PublicKey))
chat := response.Chats()[0]
members := []string{types.EncodeHex(crypto.FromECDSAPub(&alice.identity.PublicKey))}
_, err = bob.AddMembersToGroupChat(context.Background(), chat.ID, members)

View File

@ -3,6 +3,7 @@ package protocol
import (
"context"
"encoding/hex"
"errors"
"github.com/golang/protobuf/proto"
"go.uber.org/zap"
@ -15,7 +16,28 @@ import (
v1protocol "github.com/status-im/status-go/protocol/v1"
)
var ErrGroupChatAddedContacts = errors.New("group-chat: can't add members who are not mutual contacts")
func (m *Messenger) validateAddedGroupMembers(members []string) error {
for _, memberPubkey := range members {
contactID, err := contactIDFromPublicKeyString(memberPubkey)
if err != nil {
return err
}
contact, _ := m.allContacts.Load(contactID)
if contact == nil || !(contact.Added && contact.HasAddedUs) {
return ErrGroupChatAddedContacts
}
}
return nil
}
func (m *Messenger) CreateGroupChatWithMembers(ctx context.Context, name string, members []string) (*MessengerResponse, error) {
if err := m.validateAddedGroupMembers(members); err != nil {
return nil, err
}
var response MessengerResponse
logger := m.logger.With(zap.String("site", "CreateGroupChatWithMembers"))
logger.Info("Creating group chat", zap.String("name", name), zap.Any("members", members))
@ -147,6 +169,10 @@ func (m *Messenger) RemoveMemberFromGroupChat(ctx context.Context, chatID string
}
func (m *Messenger) AddMembersToGroupChat(ctx context.Context, chatID string, members []string) (*MessengerResponse, error) {
if err := m.validateAddedGroupMembers(members); err != nil {
return nil, err
}
var response MessengerResponse
logger := m.logger.With(zap.String("site", "AddMembersFromGroupChat"))
logger.Info("Adding members form group chat", zap.String("chatID", chatID), zap.Any("members", members))

View File

@ -0,0 +1,250 @@
package protocol
import (
"context"
"crypto/ecdsa"
"fmt"
"testing"
"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/common"
"github.com/status-im/status-go/protocol/tt"
"github.com/status-im/status-go/waku"
)
func TestGroupChatSuite(t *testing.T) {
suite.Run(t, new(MessengerGroupChatSuite))
}
type MessengerGroupChatSuite struct {
suite.Suite
// If one wants to send messages between different instances of Messenger,
// a single Waku service should be shared.
shh types.Waku
logger *zap.Logger
}
func (s *MessengerGroupChatSuite) newMessenger() *Messenger {
privateKey, err := crypto.GenerateKey()
s.Require().NoError(err)
messenger, err := newMessengerWithKey(s.shh, privateKey, s.logger, []Option{})
s.Require().NoError(err)
return messenger
}
func (s *MessengerGroupChatSuite) startNewMessenger() *Messenger {
messenger := s.newMessenger()
_, err := messenger.Start()
s.Require().NoError(err)
return messenger
}
func (s *MessengerGroupChatSuite) SetupTest() {
s.logger = tt.MustCreateTestLogger()
config := waku.DefaultConfig
config.MinimumAcceptedPoW = 0
shh := waku.New(&config, s.logger)
s.shh = gethbridge.NewGethWakuWrapper(shh)
s.Require().NoError(shh.Start())
}
func (s *MessengerGroupChatSuite) TearDownTest() {
}
func (s *MessengerGroupChatSuite) createGroupChat(creator *Messenger, name string, members []string) *Chat {
response, err := creator.CreateGroupChatWithMembers(context.Background(), name, members)
s.Require().NoError(err)
s.Require().Len(response.Chats(), 1)
chat := response.Chats()[0]
err = creator.SaveChat(chat)
s.Require().NoError(err)
return chat
}
func (s *MessengerGroupChatSuite) createEmptyGroupChat(creator *Messenger, name string) *Chat {
return s.createGroupChat(creator, name, []string{})
}
func (s *MessengerGroupChatSuite) verifyGroupChatCreated(member *Messenger, expectedChatActive bool) {
response, err := WaitOnMessengerResponse(
member,
func(r *MessengerResponse) bool { return len(r.Chats()) > 0 },
"chat invitation not received",
)
s.Require().NoError(err)
s.Require().Len(response.Chats(), 1)
s.Require().True(response.Chats()[0].Active == expectedChatActive)
}
func makeMutualContact(origin *Messenger, contactPubkey *ecdsa.PublicKey) error {
contact, err := BuildContactFromPublicKey(contactPubkey)
if err != nil {
return err
}
contact.Added = true
contact.HasAddedUs = true
origin.allContacts.Store(contact.ID, contact)
return nil
}
func (s *MessengerGroupChatSuite) makeContact(origin *Messenger, toAdd *Messenger) {
s.Require().NoError(makeMutualContact(origin, &toAdd.identity.PublicKey))
}
func (s *MessengerGroupChatSuite) TestGroupChatCreation() {
testCases := []struct {
name string
creatorAddedMemberAsContact bool
memberAddedCreatorAsContact bool
expectedCreationSuccess bool
expectedAddedMemberChatActive bool
}{
{
name: "not added - not added",
creatorAddedMemberAsContact: false,
memberAddedCreatorAsContact: false,
expectedCreationSuccess: false,
expectedAddedMemberChatActive: false,
},
{
name: "added - not added",
creatorAddedMemberAsContact: true,
memberAddedCreatorAsContact: false,
expectedCreationSuccess: true,
expectedAddedMemberChatActive: false,
},
{
name: "not added - added",
creatorAddedMemberAsContact: false,
memberAddedCreatorAsContact: true,
expectedCreationSuccess: false,
expectedAddedMemberChatActive: false,
},
{
name: "added - added",
creatorAddedMemberAsContact: true,
memberAddedCreatorAsContact: true,
expectedCreationSuccess: true,
expectedAddedMemberChatActive: true,
},
}
for i, testCase := range testCases {
creator := s.startNewMessenger()
member := s.startNewMessenger()
members := []string{common.PubkeyToHex(&member.identity.PublicKey)}
if testCase.creatorAddedMemberAsContact {
s.makeContact(creator, member)
}
if testCase.memberAddedCreatorAsContact {
s.makeContact(member, creator)
}
_, err := creator.CreateGroupChatWithMembers(context.Background(), fmt.Sprintf("test_group_chat_%d", i), members)
if testCase.creatorAddedMemberAsContact {
s.Require().NoError(err)
s.verifyGroupChatCreated(member, testCase.expectedAddedMemberChatActive)
} else {
s.Require().EqualError(err, "group-chat: can't add members who are not mutual contacts")
}
defer creator.Shutdown()
defer member.Shutdown()
}
}
func (s *MessengerGroupChatSuite) TestGroupChatMembersAddition() {
testCases := []struct {
name string
inviterAddedMemberAsContact bool
memberAddedInviterAsContact bool
expectedAdditionSuccess bool
expectedAddedMemberChatActive bool
}{
{
name: "not added - not added",
inviterAddedMemberAsContact: false,
memberAddedInviterAsContact: false,
expectedAdditionSuccess: false,
expectedAddedMemberChatActive: false,
},
{
name: "added - not added",
inviterAddedMemberAsContact: true,
memberAddedInviterAsContact: false,
expectedAdditionSuccess: true,
expectedAddedMemberChatActive: false,
},
{
name: "not added - added",
inviterAddedMemberAsContact: false,
memberAddedInviterAsContact: true,
expectedAdditionSuccess: false,
expectedAddedMemberChatActive: false,
},
{
name: "added - added",
inviterAddedMemberAsContact: true,
memberAddedInviterAsContact: true,
expectedAdditionSuccess: true,
expectedAddedMemberChatActive: true,
},
}
for i, testCase := range testCases {
admin := s.startNewMessenger()
inviter := s.startNewMessenger()
member := s.startNewMessenger()
members := []string{common.PubkeyToHex(&member.identity.PublicKey)}
if testCase.inviterAddedMemberAsContact {
s.makeContact(inviter, member)
}
if testCase.memberAddedInviterAsContact {
s.makeContact(member, inviter)
}
for j, inviterIsAlsoGroupCreator := range []bool{true, false} {
var groupChat *Chat
if inviterIsAlsoGroupCreator {
groupChat = s.createEmptyGroupChat(inviter, fmt.Sprintf("test_group_chat_%d_%d", i, j))
} else {
s.makeContact(admin, inviter)
groupChat = s.createGroupChat(admin, fmt.Sprintf("test_group_chat_%d_%d", i, j), []string{common.PubkeyToHex(&inviter.identity.PublicKey)})
err := inviter.SaveChat(groupChat)
s.Require().NoError(err)
}
_, err := inviter.AddMembersToGroupChat(context.Background(), groupChat.ID, members)
if testCase.inviterAddedMemberAsContact {
s.Require().NoError(err)
s.verifyGroupChatCreated(member, testCase.expectedAddedMemberChatActive)
} else {
s.Require().EqualError(err, "group-chat: can't add members who are not mutual contacts")
}
}
defer admin.Shutdown()
defer inviter.Shutdown()
defer member.Shutdown()
}
}
func (s *MessengerGroupChatSuite) TestGroupChatEdit() {
}

View File

@ -499,6 +499,9 @@ func (s *MessengerSuite) TestSendPrivateGroup() {
chat := response.Chats()[0]
key, err := crypto.GenerateKey()
s.NoError(err)
s.Require().NoError(makeMutualContact(s.m, &key.PublicKey))
members := []string{"0x" + hex.EncodeToString(crypto.FromECDSAPub(&key.PublicKey))}
_, err = s.m.AddMembersToGroupChat(context.Background(), chat.ID, members)
s.NoError(err)
@ -906,6 +909,8 @@ func (s *MessengerSuite) TestRetrieveTheirPrivateGroupChat() {
err = s.m.SaveChat(ourChat)
s.NoError(err)
s.Require().NoError(makeMutualContact(s.m, &theirMessenger.identity.PublicKey))
members := []string{"0x" + hex.EncodeToString(crypto.FromECDSAPub(&theirMessenger.identity.PublicKey))}
_, err = s.m.AddMembersToGroupChat(context.Background(), ourChat.ID, members)
s.NoError(err)
@ -972,6 +977,8 @@ func (s *MessengerSuite) TestChangeNameGroupChat() {
err = s.m.SaveChat(ourChat)
s.NoError(err)
s.Require().NoError(makeMutualContact(s.m, &theirMessenger.identity.PublicKey))
members := []string{"0x" + hex.EncodeToString(crypto.FromECDSAPub(&theirMessenger.identity.PublicKey))}
_, err = s.m.AddMembersToGroupChat(context.Background(), ourChat.ID, members)
s.NoError(err)
@ -1027,6 +1034,8 @@ func (s *MessengerSuite) TestReInvitedToGroupChat() {
err = s.m.SaveChat(ourChat)
s.NoError(err)
s.Require().NoError(makeMutualContact(s.m, &theirMessenger.identity.PublicKey))
members := []string{"0x" + hex.EncodeToString(crypto.FromECDSAPub(&theirMessenger.identity.PublicKey))}
_, err = s.m.AddMembersToGroupChat(context.Background(), ourChat.ID, members)
s.NoError(err)
@ -1486,6 +1495,11 @@ func (s *MessengerSuite) TestSharedSecretHandler() {
func (s *MessengerSuite) TestCreateGroupChatWithMembers() {
members := []string{testPK}
pubKey, err := common.HexToPubkey(testPK)
s.Require().NoError(err)
s.Require().NoError(makeMutualContact(s.m, pubKey))
response, err := s.m.CreateGroupChatWithMembers(context.Background(), "test", members)
s.NoError(err)
s.Require().Len(response.Chats(), 1)
@ -1510,6 +1524,8 @@ func (s *MessengerSuite) TestAddMembersToChat() {
s.Require().NoError(err)
members := []string{"0x" + hex.EncodeToString(crypto.FromECDSAPub(&key.PublicKey))}
s.Require().NoError(makeMutualContact(s.m, &key.PublicKey))
response, err = s.m.AddMembersToGroupChat(context.Background(), chat.ID, members)
s.Require().NoError(err)
s.Require().Len(response.Chats(), 1)

View File

@ -506,7 +506,8 @@ func (g Group) validateEvent(event MembershipUpdateEvent) bool {
case protobuf.MembershipUpdateEvent_IMAGE_CHANGED:
return g.admins.Has(event.From) && len(event.Image) > 0
case protobuf.MembershipUpdateEvent_MEMBERS_ADDED:
return g.admins.Has(event.From)
// Admins and members can add new members
return g.admins.Has(event.From) || g.members.Has(event.From)
case protobuf.MembershipUpdateEvent_MEMBER_JOINED:
return g.members.Has(event.From)
case protobuf.MembershipUpdateEvent_MEMBER_REMOVED: