feature: view only channel reactions (#4820)

* CommunityMember channel role

* make generate
This commit is contained in:
Igor Sirotin 2024-03-01 17:15:38 +00:00 committed by GitHub
parent 84713384bb
commit bdb2b261a6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 961 additions and 774 deletions

View File

@ -177,7 +177,9 @@ func (o *Community) MarshalPublicAPIJSON() ([]byte, error) {
communityItem.Encrypted = o.Encrypted() communityItem.Encrypted = o.Encrypted()
} }
for id, c := range o.config.CommunityDescription.Chats { for id, c := range o.config.CommunityDescription.Chats {
canPost, err := o.CanPost(o.config.MemberIdentity, id) // NOTE: Here `CanPost` is only set for ChatMessage. But it can be different for reactions/pin/etc.
// Consider adding more properties to `CommunityChat` to reflect that.
canPost, err := o.CanPost(o.config.MemberIdentity, id, protobuf.ApplicationMetadataMessage_CHAT_MESSAGE)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -311,7 +313,9 @@ func (o *Community) MarshalJSON() ([]byte, error) {
communityItem.Categories[id] = category communityItem.Categories[id] = category
} }
for id, c := range o.config.CommunityDescription.Chats { for id, c := range o.config.CommunityDescription.Chats {
canPost, err := o.CanPost(o.config.MemberIdentity, id) // NOTE: Here `CanPost` is only set for ChatMessage. But it can be different for reactions/pin/etc.
// Consider adding more properties to `CommunityChat` to reflect that.
canPost, err := o.CanPost(o.config.MemberIdentity, id, protobuf.ApplicationMetadataMessage_CHAT_MESSAGE)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1847,7 +1851,7 @@ func (o *Community) VerifyGrantSignature(data []byte) (*protobuf.Grant, error) {
return grant, nil return grant, nil
} }
func (o *Community) CanPost(pk *ecdsa.PublicKey, chatID string) (bool, error) { func (o *Community) CanPost(pk *ecdsa.PublicKey, chatID string, messageType protobuf.ApplicationMetadataMessage_Type) (bool, error) {
if o.config.CommunityDescription.Chats == nil { if o.config.CommunityDescription.Chats == nil {
o.config.Logger.Debug("Community.CanPost: no-chats") o.config.Logger.Debug("Community.CanPost: no-chats")
return false, nil return false, nil
@ -1886,14 +1890,24 @@ func (o *Community) CanPost(pk *ecdsa.PublicKey, chatID string) (bool, error) {
return false, nil return false, nil
} }
// Need to also be a chat member to post member, isChatMember := chat.Members[common.PubkeyToHex(pk)]
if !o.IsMemberInChat(pk, chatID) {
o.config.Logger.Debug("Community.CanPost: not a chat member", zap.String("chat-id", chatID)) switch messageType {
case protobuf.ApplicationMetadataMessage_PIN_MESSAGE:
pinAllowed := o.IsPrivilegedMember(pk) || o.AllowsAllMembersToPinMessage()
return isChatMember && pinAllowed, nil
case protobuf.ApplicationMetadataMessage_EMOJI_REACTION:
if !isChatMember {
return false, nil return false, nil
} }
isPoster := member.ChannelRole == protobuf.CommunityMember_CHANNEL_ROLE_POSTER
isViewer := member.ChannelRole == protobuf.CommunityMember_CHANNEL_ROLE_VIEWER
return isPoster || (isViewer && chat.ViewersCanPostReactions), nil
// all conditions satisfied, user can post after all default:
return true, nil return isChatMember, nil
}
} }
func (o *Community) BuildGrant(key *ecdsa.PublicKey, chatID string) ([]byte, error) { func (o *Community) BuildGrant(key *ecdsa.PublicKey, chatID string) ([]byte, error) {
@ -1968,8 +1982,8 @@ func (o *Community) isMember() bool {
return o.hasMember(o.config.MemberIdentity) return o.hasMember(o.config.MemberIdentity)
} }
func (o *Community) CanMemberIdentityPost(chatID string) (bool, error) { func (o *Community) CanMemberIdentityPost(chatID string, messageType protobuf.ApplicationMetadataMessage_Type) (bool, error) {
return o.CanPost(o.config.MemberIdentity, chatID) return o.CanPost(o.config.MemberIdentity, chatID, messageType)
} }
// CanJoin returns whether a user can join the community, only if it's // CanJoin returns whether a user can join the community, only if it's
@ -2052,7 +2066,9 @@ func (o *Community) AddMember(publicKey *ecdsa.PublicKey, roles []protobuf.Commu
return changes, nil return changes, nil
} }
func (o *Community) AddMemberToChat(chatID string, publicKey *ecdsa.PublicKey, roles []protobuf.CommunityMember_Roles) (*CommunityChanges, error) { func (o *Community) AddMemberToChat(chatID string, publicKey *ecdsa.PublicKey,
roles []protobuf.CommunityMember_Roles, channelRole protobuf.CommunityMember_ChannelRole) (*CommunityChanges, error) {
o.mutex.Lock() o.mutex.Lock()
defer o.mutex.Unlock() defer o.mutex.Unlock()
@ -2073,6 +2089,7 @@ func (o *Community) AddMemberToChat(chatID string, publicKey *ecdsa.PublicKey, r
} }
chat.Members[memberKey] = &protobuf.CommunityMember{ chat.Members[memberKey] = &protobuf.CommunityMember{
Roles: roles, Roles: roles,
ChannelRole: channelRole,
} }
changes.ChatsModified[chatID] = &CommunityChatChanges{ changes.ChatsModified[chatID] = &CommunityChatChanges{
ChatModified: chat, ChatModified: chat,

View File

@ -761,7 +761,7 @@ func (s *CommunityEncryptionKeyActionSuite) TestChannelLevelKeyActions() {
for _, member := range tc.originMembers { for _, member := range tc.originMembers {
_, err := origin.AddMember(member, []protobuf.CommunityMember_Roles{}) _, err := origin.AddMember(member, []protobuf.CommunityMember_Roles{})
s.Require().NoError(err) s.Require().NoError(err)
_, err = origin.AddMemberToChat(channelID, member, []protobuf.CommunityMember_Roles{}) _, err = origin.AddMemberToChat(channelID, member, []protobuf.CommunityMember_Roles{}, protobuf.CommunityMember_CHANNEL_ROLE_POSTER)
s.Require().NoError(err) s.Require().NoError(err)
} }
@ -772,7 +772,7 @@ func (s *CommunityEncryptionKeyActionSuite) TestChannelLevelKeyActions() {
for _, member := range tc.modifiedMembers { for _, member := range tc.modifiedMembers {
_, err := modified.AddMember(member, []protobuf.CommunityMember_Roles{}) _, err := modified.AddMember(member, []protobuf.CommunityMember_Roles{})
s.Require().NoError(err) s.Require().NoError(err)
_, err = modified.AddMemberToChat(channelID, member, []protobuf.CommunityMember_Roles{}) _, err = modified.AddMemberToChat(channelID, member, []protobuf.CommunityMember_Roles{}, protobuf.CommunityMember_CHANNEL_ROLE_POSTER)
s.Require().NoError(err) s.Require().NoError(err)
} }
@ -972,7 +972,7 @@ func (s *CommunityEncryptionKeyActionSuite) TestControlNodeChange() {
s.Require().NoError(err) s.Require().NoError(err)
} }
for _, member := range tc.channelMembers { for _, member := range tc.channelMembers {
_, err = origin.AddMemberToChat(channelID, member, []protobuf.CommunityMember_Roles{}) _, err = origin.AddMemberToChat(channelID, member, []protobuf.CommunityMember_Roles{}, protobuf.CommunityMember_CHANNEL_ROLE_POSTER)
s.Require().NoError(err) s.Require().NoError(err)
} }

View File

@ -452,9 +452,6 @@ func (s *CommunitySuite) TestCanPost() {
notMember := &s.member3.PublicKey notMember := &s.member3.PublicKey
member := &s.member1.PublicKey member := &s.member1.PublicKey
// MEMBERSHIP-NO-MEMBERSHIP-Member-> User can post
// MEMBERSHIP-NO-MEMEBRESHIP->NON member -> User can't post
testCases := []struct { testCases := []struct {
name string name string
config Config config Config
@ -463,7 +460,7 @@ func (s *CommunitySuite) TestCanPost() {
canPost bool canPost bool
}{ }{
{ {
name: "no-membership org with no-membeship chat", name: "no-membership org with no-membership chat",
config: s.configNoMembershipOrgNoMembershipChat(), config: s.configNoMembershipOrgNoMembershipChat(),
member: notMember, member: notMember,
canPost: false, canPost: false,
@ -481,7 +478,7 @@ func (s *CommunitySuite) TestCanPost() {
canPost: true, canPost: true,
}, },
{ {
name: "monsier creator can always post of course", name: "creator can always post of course",
config: s.configOnRequestOrgNoMembershipChat(), config: s.configOnRequestOrgNoMembershipChat(),
member: &s.identity.PublicKey, member: &s.identity.PublicKey,
canPost: true, canPost: true,
@ -494,7 +491,7 @@ func (s *CommunitySuite) TestCanPost() {
org, err := New(tc.config, &TimeSourceStub{}, &DescriptionEncryptorMock{}) org, err := New(tc.config, &TimeSourceStub{}, &DescriptionEncryptorMock{})
s.Require().NoError(err) s.Require().NoError(err)
canPost, err := org.CanPost(tc.member, testChatID1) canPost, err := org.CanPost(tc.member, testChatID1, protobuf.ApplicationMetadataMessage_CHAT_MESSAGE)
s.Require().Equal(tc.err, err) s.Require().Equal(tc.err, err)
s.Require().Equal(tc.canPost, canPost) s.Require().Equal(tc.canPost, canPost)
}) })

View File

@ -1046,8 +1046,12 @@ func (m *Manager) ReevaluateMembers(community *Community) (map[protobuf.Communit
isMemberAlreadyInChannel := community.IsMemberInChat(memberPubKey, channelID) isMemberAlreadyInChannel := community.IsMemberInChat(memberPubKey, channelID)
if response.ViewOnlyPermissions.Satisfied || response.ViewAndPostPermissions.Satisfied { if response.ViewOnlyPermissions.Satisfied || response.ViewAndPostPermissions.Satisfied {
channelRole := protobuf.CommunityMember_CHANNEL_ROLE_VIEWER
if response.ViewAndPostPermissions.Satisfied {
channelRole = protobuf.CommunityMember_CHANNEL_ROLE_POSTER
}
if !isMemberAlreadyInChannel { if !isMemberAlreadyInChannel {
_, err := community.AddMemberToChat(channelID, memberPubKey, []protobuf.CommunityMember_Roles{}) _, err := community.AddMemberToChat(channelID, memberPubKey, []protobuf.CommunityMember_Roles{}, channelRole)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -2315,27 +2319,37 @@ func (m *Manager) accountsSatisfyPermissionsToJoin(community *Community, account
return true, protobuf.CommunityMember_ROLE_NONE, nil return true, protobuf.CommunityMember_ROLE_NONE, nil
} }
func (m *Manager) accountsSatisfyPermissionsToJoinChannels(community *Community, accounts []*protobuf.RevealedAccount) (map[string]*protobuf.CommunityChat, error) { func (m *Manager) accountsSatisfyPermissionsToJoinChannels(community *Community, accounts []*protobuf.RevealedAccount) (map[string]*protobuf.CommunityChat, map[string]*protobuf.CommunityChat, error) {
result := make(map[string]*protobuf.CommunityChat)
accountsAndChainIDs := revealedAccountsToAccountsAndChainIDsCombination(accounts) accountsAndChainIDs := revealedAccountsToAccountsAndChainIDsCombination(accounts)
for channelID, channel := range community.config.CommunityDescription.Chats { viewChats, err := m.accountsSatisfyPermissionTypeToJoinChannels(community, accountsAndChainIDs, protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL)
channelViewOnlyPermissions := community.ChannelTokenPermissionsByType(community.IDString()+channelID, protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL) if err != nil {
channelViewAndPostPermissions := community.ChannelTokenPermissionsByType(community.IDString()+channelID, protobuf.CommunityTokenPermission_CAN_VIEW_AND_POST_CHANNEL) return nil, nil, err
channelPermissions := append(channelViewOnlyPermissions, channelViewAndPostPermissions...) }
if len(channelPermissions) > 0 { postChats, err := m.accountsSatisfyPermissionTypeToJoinChannels(community, accountsAndChainIDs, protobuf.CommunityTokenPermission_CAN_VIEW_AND_POST_CHANNEL)
permissionResponse, err := m.PermissionChecker.CheckPermissions(channelPermissions, accountsAndChainIDs, true) if err != nil {
return nil, nil, err
}
return viewChats, postChats, nil
}
func (m *Manager) accountsSatisfyPermissionTypeToJoinChannels(community *Community, accounts []*AccountChainIDsCombination, permissionType protobuf.CommunityTokenPermission_Type) (map[string]*protobuf.CommunityChat, error) {
result := make(map[string]*protobuf.CommunityChat)
for channelID, channel := range community.config.CommunityDescription.Chats {
permissions := community.ChannelTokenPermissionsByType(community.IDString()+channelID, permissionType)
if len(permissions) > 0 {
permissionResponse, err := m.PermissionChecker.CheckPermissions(permissions, accounts, true)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if permissionResponse.Satisfied { if !permissionResponse.Satisfied {
result[channelID] = channel continue
} }
} else {
result[channelID] = channel
} }
result[channelID] = channel
} }
return result, nil return result, nil
@ -2377,13 +2391,20 @@ func (m *Manager) AcceptRequestToJoin(dbRequest *RequestToJoin) (*Community, err
return nil, err return nil, err
} }
channels, err := m.accountsSatisfyPermissionsToJoinChannels(community, revealedAccounts) viewChannels, postChannels, err := m.accountsSatisfyPermissionsToJoinChannels(community, revealedAccounts)
if err != nil { if err != nil {
return nil, err return nil, err
} }
for channelID := range channels { for channelID := range viewChannels {
_, err = community.AddMemberToChat(channelID, pk, memberRoles) _, err = community.AddMemberToChat(channelID, pk, memberRoles, protobuf.CommunityMember_CHANNEL_ROLE_VIEWER)
if err != nil {
return nil, err
}
}
for channelID := range postChannels {
_, err = community.AddMemberToChat(channelID, pk, memberRoles, protobuf.CommunityMember_CHANNEL_ROLE_POSTER)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -3460,12 +3481,12 @@ func (m *Manager) RequestsToJoinForCommunityAwaitingAddresses(id types.HexBytes)
return m.persistence.RequestsToJoinForCommunityAwaitingAddresses(id) return m.persistence.RequestsToJoinForCommunityAwaitingAddresses(id)
} }
func (m *Manager) CanPost(pk *ecdsa.PublicKey, communityID string, chatID string) (bool, error) { func (m *Manager) CanPost(pk *ecdsa.PublicKey, communityID string, chatID string, messageType protobuf.ApplicationMetadataMessage_Type) (bool, error) {
community, err := m.GetByIDString(communityID) community, err := m.GetByIDString(communityID)
if err != nil { if err != nil {
return false, err return false, err
} }
return community.CanPost(pk, chatID) return community.CanPost(pk, chatID, messageType)
} }
func (m *Manager) IsEncrypted(communityID string) (bool, error) { func (m *Manager) IsEncrypted(communityID string) (bool, error) {
@ -4794,7 +4815,7 @@ func (m *Manager) ReevaluatePrivilegedMember(community *Community, tokenPermissi
// Make sure privileged user is added to every channel // Make sure privileged user is added to every channel
for channelID := range community.Chats() { for channelID := range community.Chats() {
if !community.IsMemberInChat(memberPubKey, channelID) { if !community.IsMemberInChat(memberPubKey, channelID) {
_, err := community.AddMemberToChat(channelID, memberPubKey, []protobuf.CommunityMember_Roles{privilegedRole}) _, err := community.AddMemberToChat(channelID, memberPubKey, []protobuf.CommunityMember_Roles{privilegedRole}, protobuf.CommunityMember_CHANNEL_ROLE_POSTER)
if err != nil { if err != nil {
return alreadyHasPrivilegedRole, err return alreadyHasPrivilegedRole, err
} }
@ -5002,7 +5023,7 @@ func (m *Manager) promoteSelfToControlNode(community *Community, clock uint64) (
} }
for channelID := range community.Chats() { for channelID := range community.Chats() {
_, err = community.AddMemberToChat(channelID, &m.identity.PublicKey, ownerRole) _, err = community.AddMemberToChat(channelID, &m.identity.PublicKey, ownerRole, protobuf.CommunityMember_CHANNEL_ROLE_POSTER)
if err != nil { if err != nil {
return false, err return false, err
} }

View File

@ -568,15 +568,15 @@ func waitOnCommunitiesEvent(user *Messenger, condition func(*communities.Subscri
go func() { go func() {
defer close(errCh) defer close(errCh)
subscription := user.communitiesManager.Subscribe()
for { for {
select { select {
case sub, more := <-user.communitiesManager.Subscribe(): case sub, more := <-subscription:
if !more { if !more {
errCh <- errors.New("channel closed when waiting for communities event") errCh <- errors.New("channel closed when waiting for communities event")
return return
} }
if condition(sub) { if condition(sub) {
return return
} }

View File

@ -2,6 +2,7 @@ package protocol
import ( import (
"bytes" "bytes"
"context"
"crypto/ecdsa" "crypto/ecdsa"
"errors" "errors"
"math/big" "math/big"
@ -159,11 +160,7 @@ func (s *MessengerCommunitiesTokenPermissionsSuite) SetupTest() {
_, err = s.alice.Start() _, err = s.alice.Start()
s.Require().NoError(err) s.Require().NoError(err)
s.mockedBalances = make(map[uint64]map[gethcommon.Address]map[gethcommon.Address]*hexutil.Big) s.resetMockedBalances()
s.mockedBalances[testChainID1] = make(map[gethcommon.Address]map[gethcommon.Address]*hexutil.Big)
s.mockedBalances[testChainID1][gethcommon.HexToAddress(aliceAddress1)] = make(map[gethcommon.Address]*hexutil.Big)
s.mockedBalances[testChainID1][gethcommon.HexToAddress(aliceAddress2)] = make(map[gethcommon.Address]*hexutil.Big)
s.mockedBalances[testChainID1][gethcommon.HexToAddress(bobAddress)] = make(map[gethcommon.Address]*hexutil.Big)
} }
@ -231,6 +228,14 @@ func (s *MessengerCommunitiesTokenPermissionsSuite) makeAddressSatisfyTheCriteri
s.mockedBalances[chainID][walletAddress][contractAddress] = (*hexutil.Big)(balance) s.mockedBalances[chainID][walletAddress][contractAddress] = (*hexutil.Big)(balance)
} }
func (s *MessengerCommunitiesTokenPermissionsSuite) resetMockedBalances() {
s.mockedBalances = make(map[uint64]map[gethcommon.Address]map[gethcommon.Address]*hexutil.Big)
s.mockedBalances[testChainID1] = make(map[gethcommon.Address]map[gethcommon.Address]*hexutil.Big)
s.mockedBalances[testChainID1][gethcommon.HexToAddress(aliceAddress1)] = make(map[gethcommon.Address]*hexutil.Big)
s.mockedBalances[testChainID1][gethcommon.HexToAddress(aliceAddress2)] = make(map[gethcommon.Address]*hexutil.Big)
s.mockedBalances[testChainID1][gethcommon.HexToAddress(bobAddress)] = make(map[gethcommon.Address]*hexutil.Big)
}
func (s *MessengerCommunitiesTokenPermissionsSuite) waitOnKeyDistribution(condition func(*CommunityAndKeyActions) bool) <-chan error { func (s *MessengerCommunitiesTokenPermissionsSuite) waitOnKeyDistribution(condition func(*CommunityAndKeyActions) bool) <-chan error {
testCommunitiesKeyDistributor, ok := s.owner.communitiesKeyDistributor.(*TestCommunitiesKeyDistributor) testCommunitiesKeyDistributor, ok := s.owner.communitiesKeyDistributor.(*TestCommunitiesKeyDistributor)
s.Require().True(ok) s.Require().True(ok)
@ -1002,9 +1007,26 @@ func (s *MessengerCommunitiesTokenPermissionsSuite) TestJoinCommunityAsAdminWith
s.Require().Equal(bobAddress, revealedAccounts[0].Address) s.Require().Equal(bobAddress, revealedAccounts[0].Address)
} }
func (s *MessengerCommunitiesTokenPermissionsSuite) TestViewChannelPermissions() { func (s *MessengerCommunitiesTokenPermissionsSuite) testViewChannelPermissions(viewersCanAddReactions bool) {
community, chat := s.createCommunity() community, chat := s.createCommunity()
// setup channel reactions permissions
editedChat := &protobuf.CommunityChat{
Identity: &protobuf.ChatIdentity{
DisplayName: chat.Name,
Description: chat.Description,
Emoji: chat.Emoji,
Color: chat.Color,
},
Permissions: &protobuf.CommunityPermissions{
Access: protobuf.CommunityPermissions_AUTO_ACCEPT,
},
ViewersCanPostReactions: viewersCanAddReactions,
}
_, err := s.owner.EditCommunityChat(community.ID(), chat.ID, editedChat)
s.Require().NoError(err)
// bob joins the community // bob joins the community
s.advertiseCommunityTo(community, s.bob) s.advertiseCommunityTo(community, s.bob)
s.joinCommunity(community, s.bob, bobPassword, []string{}) s.joinCommunity(community, s.bob, bobPassword, []string{})
@ -1016,12 +1038,8 @@ func (s *MessengerCommunitiesTokenPermissionsSuite) TestViewChannelPermissions()
response, err := WaitOnMessengerResponse( response, err := WaitOnMessengerResponse(
s.bob, s.bob,
func(r *MessengerResponse) bool { func(r *MessengerResponse) bool {
for _, message := range r.messages { _, ok := r.messages[msg.ID]
if message.Text == msg.Text { return ok
return true
}
}
return false
}, },
"no messages", "no messages",
) )
@ -1029,6 +1047,15 @@ func (s *MessengerCommunitiesTokenPermissionsSuite) TestViewChannelPermissions()
s.Require().Len(response.Messages(), 1) s.Require().Len(response.Messages(), 1)
s.Require().Equal(msg.Text, response.Messages()[0].Text) s.Require().Equal(msg.Text, response.Messages()[0].Text)
waitOnBobToBeKickedFromChannel := waitOnCommunitiesEvent(s.owner, func(sub *communities.Subscription) bool {
channel, ok := sub.Community.Chats()[chat.CommunityChatID()]
return ok && len(channel.Members) == 1
})
waitOnChannelToBeRekeyedOnceBobIsKicked := s.waitOnKeyDistribution(func(sub *CommunityAndKeyActions) bool {
action, ok := sub.keyActions.ChannelKeysActions[chat.CommunityChatID()]
return ok && (action.ActionType == communities.EncryptionKeyRekey || action.ActionType == communities.EncryptionKeyAdd)
})
// setup view channel permission // setup view channel permission
channelPermissionRequest := requests.CreateCommunityTokenPermission{ channelPermissionRequest := requests.CreateCommunityTokenPermission{
CommunityID: community.ID(), CommunityID: community.ID(),
@ -1045,24 +1072,6 @@ func (s *MessengerCommunitiesTokenPermissionsSuite) TestViewChannelPermissions()
ChatIds: []string{chat.ID}, ChatIds: []string{chat.ID},
} }
waitOnBobToBeKickedFromChannel := waitOnCommunitiesEvent(s.owner, func(sub *communities.Subscription) bool {
for channelID, channel := range sub.Community.Chats() {
if channelID == chat.CommunityChatID() && len(channel.Members) == 1 {
return true
}
}
return false
})
waitOnChannelToBeRekeyedOnceBobIsKicked := s.waitOnKeyDistribution(func(sub *CommunityAndKeyActions) bool {
for channelID, action := range sub.keyActions.ChannelKeysActions {
// We both listen for Rekey or Add, since the first time around the community goes from non-encrypted to encrypted, and that's an Add
if channelID == chat.CommunityChatID() && (action.ActionType == communities.EncryptionKeyRekey || action.ActionType == communities.EncryptionKeyAdd) {
return true
}
}
return false
})
response, err = s.owner.CreateCommunityTokenPermission(&channelPermissionRequest) response, err = s.owner.CreateCommunityTokenPermission(&channelPermissionRequest)
s.Require().NoError(err) s.Require().NoError(err)
s.Require().Len(response.Communities(), 1) s.Require().Len(response.Communities(), 1)
@ -1080,15 +1089,14 @@ func (s *MessengerCommunitiesTokenPermissionsSuite) TestViewChannelPermissions()
_, err = WaitOnMessengerResponse( _, err = WaitOnMessengerResponse(
s.bob, s.bob,
func(r *MessengerResponse) bool { func(r *MessengerResponse) bool {
community, err := s.bob.GetCommunityByID(community.ID()) c, err := s.bob.GetCommunityByID(community.ID())
if err != nil { if err != nil {
return false return false
} }
if c == nil {
if community == nil {
return false return false
} }
channel := community.Chats()[chat.CommunityChatID()] channel := c.Chats()[chat.CommunityChatID()]
return channel != nil && len(channel.Members) == 0 return channel != nil && len(channel.Members) == 0
}, },
"no community that satisfies criteria", "no community that satisfies criteria",
@ -1097,19 +1105,15 @@ func (s *MessengerCommunitiesTokenPermissionsSuite) TestViewChannelPermissions()
// make bob satisfy channel criteria // make bob satisfy channel criteria
s.makeAddressSatisfyTheCriteria(testChainID1, bobAddress, channelPermissionRequest.TokenCriteria[0]) s.makeAddressSatisfyTheCriteria(testChainID1, bobAddress, channelPermissionRequest.TokenCriteria[0])
defer s.resetMockedBalances() // reset mocked balances, this test in run with different test cases
waitOnChannelKeyToBeDistributedToBob := s.waitOnKeyDistribution(func(sub *CommunityAndKeyActions) bool { waitOnChannelKeyToBeDistributedToBob := s.waitOnKeyDistribution(func(sub *CommunityAndKeyActions) bool {
for channelID, action := range sub.keyActions.ChannelKeysActions { action, ok := sub.keyActions.ChannelKeysActions[chat.CommunityChatID()]
if channelID == chat.CommunityChatID() && action.ActionType == communities.EncryptionKeySendToMembers { if !ok || action.ActionType != communities.EncryptionKeySendToMembers {
for memberPubKey := range action.Members {
if memberPubKey == common.PubkeyToHex(&s.bob.identity.PublicKey) {
return true
}
}
}
}
return false return false
}
_, ok = action.Members[common.PubkeyToHex(&s.bob.identity.PublicKey)]
return ok
}) })
// force owner to reevaluate channel members // force owner to reevaluate channel members
@ -1129,18 +1133,64 @@ func (s *MessengerCommunitiesTokenPermissionsSuite) TestViewChannelPermissions()
response, err = WaitOnMessengerResponse( response, err = WaitOnMessengerResponse(
s.bob, s.bob,
func(r *MessengerResponse) bool { func(r *MessengerResponse) bool {
for _, message := range r.messages { _, ok := r.messages[msg.ID]
if message.Text == msg.Text { return ok
return true
}
}
return false
}, },
"no messages", "no messages",
) )
s.Require().NoError(err) s.Require().NoError(err)
s.Require().Len(response.Messages(), 1) s.Require().Len(response.Messages(), 1)
s.Require().Equal(msg.Text, response.Messages()[0].Text) s.Require().Equal(msg.Text, response.Messages()[0].Text)
// bob can/can't post reactions
response, err = s.bob.SendEmojiReaction(context.Background(), chat.ID, msg.ID, protobuf.EmojiReaction_THUMBS_UP)
if !viewersCanAddReactions {
s.Require().Error(err)
} else {
s.Require().NoError(err)
s.Require().Len(response.emojiReactions, 1)
reactionMessage := response.EmojiReactions()[0]
response, err = WaitOnMessengerResponse(
s.owner,
func(r *MessengerResponse) bool {
_, ok := r.emojiReactions[reactionMessage.ID()]
return ok
},
"no reactions received",
)
if viewersCanAddReactions {
s.Require().NoError(err)
s.Require().Len(response.EmojiReactions(), 1)
s.Require().Equal(response.EmojiReactions()[0].Type, protobuf.EmojiReaction_THUMBS_UP)
} else {
s.Require().Error(err)
s.Require().Len(response.EmojiReactions(), 0)
}
}
}
func (s *MessengerCommunitiesTokenPermissionsSuite) TestViewChannelPermissions() {
testCases := []struct {
name string
viewersCanPostReactions bool
}{
{
name: "viewers are allowed to post reactions",
viewersCanPostReactions: true,
},
{
name: "viewers are forbidden to post reactions",
viewersCanPostReactions: false,
},
}
for _, tc := range testCases {
s.T().Run(tc.name, func(*testing.T) {
s.testViewChannelPermissions(tc.viewersCanPostReactions)
})
}
} }
func (s *MessengerCommunitiesTokenPermissionsSuite) testReevaluateMemberPrivilegedRoleInOpenCommunity(permissionType protobuf.CommunityTokenPermission_Type) { func (s *MessengerCommunitiesTokenPermissionsSuite) testReevaluateMemberPrivilegedRoleInOpenCommunity(permissionType protobuf.CommunityTokenPermission_Type) {

View File

@ -2227,6 +2227,7 @@ func (m *Messenger) dispatchMessage(ctx context.Context, rawMessage common.RawMe
if err != nil { if err != nil {
return rawMessage, err return rawMessage, err
} }
case ChatTypeCommunityChat: case ChatTypeCommunityChat:
community, err := m.communitiesManager.GetByIDString(chat.CommunityID) community, err := m.communitiesManager.GetByIDString(chat.CommunityID)
if err != nil { if err != nil {
@ -2234,14 +2235,18 @@ func (m *Messenger) dispatchMessage(ctx context.Context, rawMessage common.RawMe
} }
rawMessage.PubsubTopic = community.PubsubTopic() rawMessage.PubsubTopic = community.PubsubTopic()
canPost, err := m.communitiesManager.CanPost(&m.identity.PublicKey, chat.CommunityID, chat.CommunityChatID()) canPost, err := m.communitiesManager.CanPost(&m.identity.PublicKey, chat.CommunityID, chat.CommunityChatID(), rawMessage.MessageType)
if err != nil { if err != nil {
return rawMessage, err return rawMessage, err
} }
if !canPost { if !canPost {
m.logger.Error("can't post on chat", zap.String("chat-id", chat.ID), zap.String("chat-name", chat.Name)) m.logger.Error("can't post on chat",
return rawMessage, errors.New("can't post on chat") zap.String("chatID", chat.ID),
zap.String("chatName", chat.Name),
zap.Any("messageType", rawMessage.MessageType),
)
return rawMessage, fmt.Errorf("can't post message type '%d' on chat '%s'", rawMessage.MessageType, chat.ID)
} }
logger.Debug("sending community chat message", zap.String("chatName", chat.Name)) logger.Debug("sending community chat message", zap.String("chatName", chat.Name))
@ -5688,143 +5693,6 @@ func generateAliasAndIdenticon(pk string) (string, string, error) {
} }
func (m *Messenger) SendEmojiReaction(ctx context.Context, chatID, messageID string, emojiID protobuf.EmojiReaction_Type) (*MessengerResponse, error) {
var response MessengerResponse
chat, ok := m.allChats.Load(chatID)
if !ok {
return nil, ErrChatNotFound
}
clock, _ := chat.NextClockAndTimestamp(m.getTimesource())
emojiR := &EmojiReaction{
EmojiReaction: &protobuf.EmojiReaction{
Clock: clock,
MessageId: messageID,
ChatId: chatID,
Type: emojiID,
},
LocalChatID: chatID,
From: types.EncodeHex(crypto.FromECDSAPub(&m.identity.PublicKey)),
}
encodedMessage, err := m.encodeChatEntity(chat, emojiR)
if err != nil {
return nil, err
}
_, err = m.dispatchMessage(ctx, common.RawMessage{
LocalChatID: chatID,
Payload: encodedMessage,
SkipGroupMessageWrap: true,
MessageType: protobuf.ApplicationMetadataMessage_EMOJI_REACTION,
// Don't resend using datasync, that would create quite a lot
// of traffic if clicking too eagelry
ResendAutomatically: false,
})
if err != nil {
return nil, err
}
response.AddEmojiReaction(emojiR)
response.AddChat(chat)
err = m.persistence.SaveEmojiReaction(emojiR)
if err != nil {
return nil, errors.Wrap(err, "Can't save emoji reaction in db")
}
return &response, nil
}
func (m *Messenger) EmojiReactionsByChatID(chatID string, cursor string, limit int) ([]*EmojiReaction, error) {
chat, err := m.persistence.Chat(chatID)
if err != nil {
return nil, err
}
if chat.Timeline() {
var chatIDs = []string{"@" + contactIDFromPublicKey(&m.identity.PublicKey)}
m.allContacts.Range(func(contactID string, contact *Contact) (shouldContinue bool) {
if contact.added() {
chatIDs = append(chatIDs, "@"+contact.ID)
}
return true
})
return m.persistence.EmojiReactionsByChatIDs(chatIDs, cursor, limit)
}
return m.persistence.EmojiReactionsByChatID(chatID, cursor, limit)
}
func (m *Messenger) EmojiReactionsByChatIDMessageID(chatID string, messageID string) ([]*EmojiReaction, error) {
_, err := m.persistence.Chat(chatID)
if err != nil {
return nil, err
}
return m.persistence.EmojiReactionsByChatIDMessageID(chatID, messageID)
}
func (m *Messenger) SendEmojiReactionRetraction(ctx context.Context, emojiReactionID string) (*MessengerResponse, error) {
emojiR, err := m.persistence.EmojiReactionByID(emojiReactionID)
if err != nil {
return nil, err
}
// Check that the sender is the key owner
pk := types.EncodeHex(crypto.FromECDSAPub(&m.identity.PublicKey))
if emojiR.From != pk {
return nil, errors.Errorf("identity mismatch, "+
"emoji reactions can only be retracted by the reaction sender, "+
"emoji reaction sent by '%s', current identity '%s'",
emojiR.From, pk,
)
}
// Get chat and clock
chat, ok := m.allChats.Load(emojiR.GetChatId())
if !ok {
return nil, ErrChatNotFound
}
clock, _ := chat.NextClockAndTimestamp(m.getTimesource())
// Update the relevant fields
emojiR.Clock = clock
emojiR.Retracted = true
encodedMessage, err := m.encodeChatEntity(chat, emojiR)
if err != nil {
return nil, err
}
// Send the marshalled EmojiReactionRetraction protobuf
_, err = m.dispatchMessage(ctx, common.RawMessage{
LocalChatID: emojiR.GetChatId(),
Payload: encodedMessage,
SkipGroupMessageWrap: true,
MessageType: protobuf.ApplicationMetadataMessage_EMOJI_REACTION,
// Don't resend using datasync, that would create quite a lot
// of traffic if clicking too eagelry
ResendAutomatically: false,
})
if err != nil {
return nil, err
}
// Update MessengerResponse
response := MessengerResponse{}
emojiR.Retracted = true
response.AddEmojiReaction(emojiR)
response.AddChat(chat)
// Persist retraction state for emoji reaction
err = m.persistence.SaveEmojiReaction(emojiR)
if err != nil {
return nil, err
}
return &response, nil
}
func (m *Messenger) encodeChatEntity(chat *Chat, message common.ChatEntity) ([]byte, error) { func (m *Messenger) encodeChatEntity(chat *Chat, message common.ChatEntity) ([]byte, error) {
var encodedMessage []byte var encodedMessage []byte
var err error var err error

View File

@ -0,0 +1,149 @@
package protocol
import (
"context"
"github.com/pkg/errors"
"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/protobuf"
)
func (m *Messenger) SendEmojiReaction(ctx context.Context, chatID, messageID string, emojiID protobuf.EmojiReaction_Type) (*MessengerResponse, error) {
var response MessengerResponse
chat, ok := m.allChats.Load(chatID)
if !ok {
return nil, ErrChatNotFound
}
clock, _ := chat.NextClockAndTimestamp(m.getTimesource())
emojiR := &EmojiReaction{
EmojiReaction: &protobuf.EmojiReaction{
Clock: clock,
MessageId: messageID,
ChatId: chatID,
Type: emojiID,
},
LocalChatID: chatID,
From: types.EncodeHex(crypto.FromECDSAPub(&m.identity.PublicKey)),
}
encodedMessage, err := m.encodeChatEntity(chat, emojiR)
if err != nil {
return nil, err
}
_, err = m.dispatchMessage(ctx, common.RawMessage{
LocalChatID: chatID,
Payload: encodedMessage,
SkipGroupMessageWrap: true,
MessageType: protobuf.ApplicationMetadataMessage_EMOJI_REACTION,
// Don't resend using datasync, that would create quite a lot
// of traffic if clicking too eagelry
ResendAutomatically: false,
})
if err != nil {
return nil, err
}
response.AddEmojiReaction(emojiR)
response.AddChat(chat)
err = m.persistence.SaveEmojiReaction(emojiR)
if err != nil {
return nil, errors.Wrap(err, "Can't save emoji reaction in db")
}
return &response, nil
}
func (m *Messenger) EmojiReactionsByChatID(chatID string, cursor string, limit int) ([]*EmojiReaction, error) {
chat, err := m.persistence.Chat(chatID)
if err != nil {
return nil, err
}
if chat.Timeline() {
var chatIDs = []string{"@" + contactIDFromPublicKey(&m.identity.PublicKey)}
m.allContacts.Range(func(contactID string, contact *Contact) (shouldContinue bool) {
if contact.added() {
chatIDs = append(chatIDs, "@"+contact.ID)
}
return true
})
return m.persistence.EmojiReactionsByChatIDs(chatIDs, cursor, limit)
}
return m.persistence.EmojiReactionsByChatID(chatID, cursor, limit)
}
func (m *Messenger) EmojiReactionsByChatIDMessageID(chatID string, messageID string) ([]*EmojiReaction, error) {
_, err := m.persistence.Chat(chatID)
if err != nil {
return nil, err
}
return m.persistence.EmojiReactionsByChatIDMessageID(chatID, messageID)
}
func (m *Messenger) SendEmojiReactionRetraction(ctx context.Context, emojiReactionID string) (*MessengerResponse, error) {
emojiR, err := m.persistence.EmojiReactionByID(emojiReactionID)
if err != nil {
return nil, err
}
// Check that the sender is the key owner
pk := types.EncodeHex(crypto.FromECDSAPub(&m.identity.PublicKey))
if emojiR.From != pk {
return nil, errors.Errorf("identity mismatch, "+
"emoji reactions can only be retracted by the reaction sender, "+
"emoji reaction sent by '%s', current identity '%s'",
emojiR.From, pk,
)
}
// Get chat and clock
chat, ok := m.allChats.Load(emojiR.GetChatId())
if !ok {
return nil, ErrChatNotFound
}
clock, _ := chat.NextClockAndTimestamp(m.getTimesource())
// Update the relevant fields
emojiR.Clock = clock
emojiR.Retracted = true
encodedMessage, err := m.encodeChatEntity(chat, emojiR)
if err != nil {
return nil, err
}
// Send the marshalled EmojiReactionRetraction protobuf
_, err = m.dispatchMessage(ctx, common.RawMessage{
LocalChatID: emojiR.GetChatId(),
Payload: encodedMessage,
SkipGroupMessageWrap: true,
MessageType: protobuf.ApplicationMetadataMessage_EMOJI_REACTION,
// Don't resend using datasync, that would create quite a lot
// of traffic if clicking too eagelry
ResendAutomatically: false,
})
if err != nil {
return nil, err
}
// Update MessengerResponse
response := MessengerResponse{}
emojiR.Retracted = true
response.AddEmojiReaction(emojiR)
response.AddChat(chat)
// Persist retraction state for emoji reaction
err = m.persistence.SaveEmojiReaction(emojiR)
if err != nil {
return nil, err
}
return &response, nil
}

View File

@ -393,7 +393,26 @@ func (m *Messenger) handleCommandMessage(state *ReceivedMessageState, message *c
if err := message.PrepareContent(common.PubkeyToHex(&m.identity.PublicKey)); err != nil { if err := message.PrepareContent(common.PubkeyToHex(&m.identity.PublicKey)); err != nil {
return fmt.Errorf("failed to prepare content: %v", err) return fmt.Errorf("failed to prepare content: %v", err)
} }
chat, err := m.matchChatEntity(message)
// Get Application layer messageType from commandState
// Currently this is not really used in `matchChatEntity`, but I did want to pass UNKNOWN there.
var messageType protobuf.ApplicationMetadataMessage_Type
switch message.CommandParameters.CommandState {
case common.CommandStateRequestAddressForTransaction:
messageType = protobuf.ApplicationMetadataMessage_REQUEST_ADDRESS_FOR_TRANSACTION
case common.CommandStateRequestAddressForTransactionAccepted:
messageType = protobuf.ApplicationMetadataMessage_ACCEPT_REQUEST_ADDRESS_FOR_TRANSACTION
case common.CommandStateRequestAddressForTransactionDeclined:
messageType = protobuf.ApplicationMetadataMessage_DECLINE_REQUEST_ADDRESS_FOR_TRANSACTION
case common.CommandStateRequestTransaction:
messageType = protobuf.ApplicationMetadataMessage_REQUEST_TRANSACTION
case common.CommandStateRequestTransactionDeclined:
messageType = protobuf.ApplicationMetadataMessage_DECLINE_REQUEST_TRANSACTION
default:
messageType = protobuf.ApplicationMetadataMessage_UNKNOWN
}
chat, err := m.matchChatEntity(message, messageType)
if err != nil { if err != nil {
return err return err
} }
@ -862,7 +881,7 @@ func (m *Messenger) handlePinMessage(pinner *Contact, whisperTimestamp uint64, r
Alias: pinner.Alias, Alias: pinner.Alias,
} }
chat, err := m.matchChatEntity(pinMessage) chat, err := m.matchChatEntity(pinMessage, protobuf.ApplicationMetadataMessage_PIN_MESSAGE)
if err != nil { if err != nil {
return err // matchChatEntity returns a descriptive error message return err // matchChatEntity returns a descriptive error message
} }
@ -2127,7 +2146,7 @@ func (m *Messenger) handleChatMessage(state *ReceivedMessageState, forceSeen boo
} }
} }
chat, err := m.matchChatEntity(receivedMessage) chat, err := m.matchChatEntity(receivedMessage, protobuf.ApplicationMetadataMessage_CHAT_MESSAGE)
if err != nil { if err != nil {
return err // matchChatEntity returns a descriptive error message return err // matchChatEntity returns a descriptive error message
} }
@ -2684,7 +2703,7 @@ func (m *Messenger) HandleDeclineRequestTransaction(messageState *ReceivedMessag
return m.handleCommandMessage(messageState, oldMessage) return m.handleCommandMessage(messageState, oldMessage)
} }
func (m *Messenger) matchChatEntity(chatEntity common.ChatEntity) (*Chat, error) { func (m *Messenger) matchChatEntity(chatEntity common.ChatEntity, messageType protobuf.ApplicationMetadataMessage_Type) (*Chat, error) {
if chatEntity.GetSigPubKey() == nil { if chatEntity.GetSigPubKey() == nil {
m.logger.Error("public key can't be empty") m.logger.Error("public key can't be empty")
return nil, errors.New("received a chatEntity with empty public key") return nil, errors.New("received a chatEntity with empty public key")
@ -2753,7 +2772,7 @@ func (m *Messenger) matchChatEntity(chatEntity common.ChatEntity) (*Chat, error)
return nil, errors.New("not an community chat") return nil, errors.New("not an community chat")
} }
canPost, err := m.communitiesManager.CanPost(chatEntity.GetSigPubKey(), chat.CommunityID, chat.CommunityChatID()) canPost, err := m.communitiesManager.CanPost(chatEntity.GetSigPubKey(), chat.CommunityID, chat.CommunityChatID(), messageType)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -2762,21 +2781,8 @@ func (m *Messenger) matchChatEntity(chatEntity common.ChatEntity) (*Chat, error)
return nil, errors.New("user can't post in community") return nil, errors.New("user can't post in community")
} }
_, isPinMessage := chatEntity.(*common.PinMessage)
if isPinMessage {
community, err := m.communitiesManager.GetByIDString(chat.CommunityID)
if err != nil {
return nil, err
}
hasPermission := community.IsPrivilegedMember(chatEntity.GetSigPubKey())
pinMessageAllowed := community.AllowsAllMembersToPinMessage()
if !hasPermission && !pinMessageAllowed {
return nil, errors.New("user can't pin message")
}
}
return chat, nil return chat, nil
case chatEntity.GetMessageType() == protobuf.MessageType_PRIVATE_GROUP: case chatEntity.GetMessageType() == protobuf.MessageType_PRIVATE_GROUP:
// In the case of a group chatEntity, ChatID is the same for all messages belonging to a group. // In the case of a group chatEntity, ChatID is the same for all messages belonging to a group.
// It needs to be verified if the signature public key belongs to the chat. // It needs to be verified if the signature public key belongs to the chat.
@ -2853,7 +2859,7 @@ func (m *Messenger) HandleEmojiReaction(state *ReceivedMessageState, pbEmojiR *p
return nil return nil
} }
chat, err := m.matchChatEntity(emojiReaction) chat, err := m.matchChatEntity(emojiReaction, protobuf.ApplicationMetadataMessage_EMOJI_REACTION)
if err != nil { if err != nil {
return err // matchChatEntity returns a descriptive error message return err // matchChatEntity returns a descriptive error message
} }

View File

@ -31,24 +31,6 @@ func (m *Messenger) sendPinMessage(ctx context.Context, message *common.PinMessa
return nil, errors.New("chat not found") return nil, errors.New("chat not found")
} }
if chat.CommunityChat() {
community, err := m.communitiesManager.GetByIDString(chat.CommunityID)
if err != nil {
return nil, err
}
hasPermission := community.IsPrivilegedMember(&m.identity.PublicKey)
pinMessageAllowed := community.AllowsAllMembersToPinMessage()
canPost, err := community.CanPost(&m.identity.PublicKey, chat.CommunityChatID())
if err != nil {
return nil, err
}
if !canPost && !pinMessageAllowed && !hasPermission {
return nil, errors.New("can't pin message")
}
}
err := m.handleStandaloneChatIdentity(chat) err := m.handleStandaloneChatIdentity(chat)
if err != nil { if err != nil {
return nil, err return nil, err

File diff suppressed because it is too large Load Diff

View File

@ -23,9 +23,16 @@ message CommunityMember {
ROLE_ADMIN = 4; ROLE_ADMIN = 4;
ROLE_TOKEN_MASTER = 5; ROLE_TOKEN_MASTER = 5;
} }
enum ChannelRole {
// We make POSTER the first role to be the default one.
// This is for backwards compatibility. Older protobufs won't have this field and will default to 0.
CHANNEL_ROLE_POSTER = 0;
CHANNEL_ROLE_VIEWER = 1;
}
repeated Roles roles = 1; repeated Roles roles = 1;
repeated RevealedAccount revealed_accounts = 2 [deprecated = true]; repeated RevealedAccount revealed_accounts = 2 [deprecated = true];
uint64 last_update_clock = 3; uint64 last_update_clock = 3;
ChannelRole channel_role = 4;
} }
message CommunityTokenMetadata { message CommunityTokenMetadata {
@ -121,6 +128,7 @@ message CommunityChat {
ChatIdentity identity = 3; ChatIdentity identity = 3;
string category_id = 4; string category_id = 4;
int32 position = 5; int32 position = 5;
bool viewers_can_post_reactions = 6;
} }
message CommunityCategory { message CommunityCategory {

View File

@ -77,7 +77,11 @@ type Chat struct {
FirstMessageTimestamp uint32 `json:"firstMessageTimestamp,omitempty"` FirstMessageTimestamp uint32 `json:"firstMessageTimestamp,omitempty"`
Highlight bool `json:"highlight,omitempty"` Highlight bool `json:"highlight,omitempty"`
PinnedMessages *PinnedMessages `json:"pinnedMessages,omitempty"` PinnedMessages *PinnedMessages `json:"pinnedMessages,omitempty"`
// Deprecated: CanPost is deprecated in favor of CanPostMessages/CanPostReactions/etc.
// For now CanPost will equal to CanPostMessages.
CanPost bool `json:"canPost"` CanPost bool `json:"canPost"`
CanPostMessages bool `json:"canPostMessages"`
CanPostReactions bool `json:"canPostReactions"`
Base64Image string `json:"image,omitempty"` Base64Image string `json:"image,omitempty"`
} }
@ -482,7 +486,12 @@ func (chat *Chat) populateCommunityFields(community *communities.Community) erro
return nil return nil
} }
canPost, err := community.CanMemberIdentityPost(chat.ID) canPostMessages, err := community.CanMemberIdentityPost(chat.ID, protobuf.ApplicationMetadataMessage_CHAT_MESSAGE)
if err != nil {
return err
}
canPostReactions, err := community.CanMemberIdentityPost(chat.ID, protobuf.ApplicationMetadataMessage_EMOJI_REACTION)
if err != nil { if err != nil {
return err return err
} }
@ -493,7 +502,9 @@ func (chat *Chat) populateCommunityFields(community *communities.Community) erro
chat.Emoji = commChat.Identity.Emoji chat.Emoji = commChat.Identity.Emoji
chat.Name = commChat.Identity.DisplayName chat.Name = commChat.Identity.DisplayName
chat.Description = commChat.Identity.Description chat.Description = commChat.Identity.Description
chat.CanPost = canPost chat.CanPost = canPostMessages
chat.CanPostMessages = canPostMessages
chat.CanPostReactions = canPostReactions
return nil return nil
} }