feature: view only channel reactions (#4820)
* CommunityMember channel role * make generate
This commit is contained in:
parent
84713384bb
commit
bdb2b261a6
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
})
|
})
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue