2338 lines
81 KiB
Go
2338 lines
81 KiB
Go
package protocol
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/ecdsa"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/suite"
|
|
"go.uber.org/zap"
|
|
"golang.org/x/exp/maps"
|
|
|
|
gethcommon "github.com/ethereum/go-ethereum/common"
|
|
hexutil "github.com/ethereum/go-ethereum/common/hexutil"
|
|
gethbridge "github.com/status-im/status-go/eth-node/bridge/geth"
|
|
"github.com/status-im/status-go/eth-node/crypto"
|
|
"github.com/status-im/status-go/eth-node/types"
|
|
"github.com/status-im/status-go/params"
|
|
"github.com/status-im/status-go/protocol/common"
|
|
"github.com/status-im/status-go/protocol/common/shard"
|
|
"github.com/status-im/status-go/protocol/communities"
|
|
"github.com/status-im/status-go/protocol/protobuf"
|
|
"github.com/status-im/status-go/protocol/requests"
|
|
"github.com/status-im/status-go/protocol/transport"
|
|
"github.com/status-im/status-go/protocol/tt"
|
|
"github.com/status-im/status-go/services/wallet/thirdparty"
|
|
)
|
|
|
|
const testChainID1 = 1
|
|
|
|
const ownerPassword = "123456"
|
|
const alicePassword = "qwerty"
|
|
const bobPassword = "bob123"
|
|
|
|
const ownerAddress = "0x0100000000000000000000000000000000000000"
|
|
const aliceAddress1 = "0x0200000000000000000000000000000000000000"
|
|
const aliceAddress2 = "0x0210000000000000000000000000000000000000"
|
|
const bobAddress = "0x0300000000000000000000000000000000000000"
|
|
|
|
type CommunityAndKeyActions struct {
|
|
community *communities.Community
|
|
keyActions *communities.EncryptionKeyActions
|
|
}
|
|
|
|
type TestCommunitiesKeyDistributor struct {
|
|
CommunitiesKeyDistributorImpl
|
|
|
|
subscriptions map[chan *CommunityAndKeyActions]bool
|
|
mutex sync.RWMutex
|
|
}
|
|
|
|
func (tckd *TestCommunitiesKeyDistributor) Generate(community *communities.Community, keyActions *communities.EncryptionKeyActions) error {
|
|
return tckd.CommunitiesKeyDistributorImpl.Generate(community, keyActions)
|
|
}
|
|
|
|
func (tckd *TestCommunitiesKeyDistributor) Distribute(community *communities.Community, keyActions *communities.EncryptionKeyActions) error {
|
|
err := tckd.CommunitiesKeyDistributorImpl.Distribute(community, keyActions)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// notify distribute finished
|
|
tckd.mutex.RLock()
|
|
for s := range tckd.subscriptions {
|
|
s <- &CommunityAndKeyActions{
|
|
community: community,
|
|
keyActions: keyActions,
|
|
}
|
|
}
|
|
tckd.mutex.RUnlock()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (tckd *TestCommunitiesKeyDistributor) subscribeToKeyDistribution() chan *CommunityAndKeyActions {
|
|
subscription := make(chan *CommunityAndKeyActions, 40)
|
|
tckd.mutex.Lock()
|
|
defer tckd.mutex.Unlock() // Ensure the mutex is always unlocked
|
|
tckd.subscriptions[subscription] = true
|
|
return subscription
|
|
}
|
|
|
|
func (tckd *TestCommunitiesKeyDistributor) unsubscribeFromKeyDistribution(subscription chan *CommunityAndKeyActions) {
|
|
tckd.mutex.Lock()
|
|
delete(tckd.subscriptions, subscription)
|
|
tckd.mutex.Unlock()
|
|
close(subscription)
|
|
}
|
|
|
|
func (tckd *TestCommunitiesKeyDistributor) waitOnKeyDistribution(condition func(*CommunityAndKeyActions) bool) <-chan error {
|
|
errCh := make(chan error, 1)
|
|
|
|
subscription := tckd.subscribeToKeyDistribution()
|
|
|
|
go func() {
|
|
defer func() {
|
|
close(errCh)
|
|
|
|
tckd.unsubscribeFromKeyDistribution(subscription)
|
|
}()
|
|
|
|
for {
|
|
select {
|
|
case s, more := <-subscription:
|
|
if !more {
|
|
errCh <- errors.New("channel closed when waiting for key distribution")
|
|
return
|
|
}
|
|
|
|
if condition(s) {
|
|
return
|
|
}
|
|
|
|
case <-time.After(5 * time.Second):
|
|
errCh <- errors.New("timed out when waiting for key distribution")
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
return errCh
|
|
}
|
|
|
|
func TestMessengerCommunitiesTokenPermissionsSuite(t *testing.T) {
|
|
suite.Run(t, new(MessengerCommunitiesTokenPermissionsSuite))
|
|
}
|
|
|
|
type MessengerCommunitiesTokenPermissionsSuite struct {
|
|
suite.Suite
|
|
owner *Messenger
|
|
bob *Messenger
|
|
alice *Messenger
|
|
|
|
ownerWaku types.Waku
|
|
bobWaku types.Waku
|
|
aliceWaku types.Waku
|
|
|
|
logger *zap.Logger
|
|
|
|
mockedBalances communities.BalancesByChain
|
|
mockedCollectibles communities.CollectiblesByChain
|
|
collectiblesServiceMock *CollectiblesServiceMock
|
|
collectiblesManagerMock *CollectiblesManagerMock
|
|
accountsTestData map[string][]string
|
|
accountsPasswords map[string]string
|
|
}
|
|
|
|
func (s *MessengerCommunitiesTokenPermissionsSuite) SetupTest() {
|
|
// Initialize with nil to avoid panics in TearDownTest
|
|
s.owner = nil
|
|
s.bob = nil
|
|
s.alice = nil
|
|
s.ownerWaku = nil
|
|
s.bobWaku = nil
|
|
s.aliceWaku = nil
|
|
|
|
s.accountsTestData = make(map[string][]string)
|
|
s.accountsPasswords = make(map[string]string)
|
|
|
|
s.mockedCollectibles = make(communities.CollectiblesByChain)
|
|
s.collectiblesManagerMock = &CollectiblesManagerMock{
|
|
Collectibles: &s.mockedCollectibles,
|
|
}
|
|
s.resetMockedBalances()
|
|
|
|
s.logger = tt.MustCreateTestLogger()
|
|
|
|
wakuNodes := CreateWakuV2Network(&s.Suite, s.logger, []string{"owner", "bob", "alice"})
|
|
|
|
s.ownerWaku = wakuNodes[0]
|
|
s.owner = s.newMessenger(ownerPassword, []string{ownerAddress}, s.ownerWaku, "owner", []Option{})
|
|
|
|
s.bobWaku = wakuNodes[1]
|
|
s.bob = s.newMessenger(bobPassword, []string{bobAddress}, s.bobWaku, "bob", []Option{})
|
|
s.bob.EnableBackedupMessagesProcessing()
|
|
|
|
s.aliceWaku = wakuNodes[2]
|
|
s.alice = s.newMessenger(alicePassword, []string{aliceAddress1, aliceAddress2}, s.aliceWaku, "alice", []Option{})
|
|
|
|
_, err := s.owner.Start()
|
|
s.Require().NoError(err)
|
|
_, err = s.bob.Start()
|
|
s.Require().NoError(err)
|
|
_, err = s.alice.Start()
|
|
s.Require().NoError(err)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesTokenPermissionsSuite) TearDownTest() {
|
|
TearDownMessenger(&s.Suite, s.owner)
|
|
TearDownMessenger(&s.Suite, s.bob)
|
|
TearDownMessenger(&s.Suite, s.alice)
|
|
if s.ownerWaku != nil {
|
|
s.Require().NoError(gethbridge.GetGethWakuV2From(s.ownerWaku).Stop())
|
|
}
|
|
if s.bobWaku != nil {
|
|
s.Require().NoError(gethbridge.GetGethWakuV2From(s.bobWaku).Stop())
|
|
}
|
|
if s.aliceWaku != nil {
|
|
s.Require().NoError(gethbridge.GetGethWakuV2From(s.aliceWaku).Stop())
|
|
}
|
|
_ = s.logger.Sync()
|
|
}
|
|
|
|
func (s *MessengerCommunitiesTokenPermissionsSuite) newMessenger(password string, walletAddresses []string, waku types.Waku, name string, extraOptions []Option) *Messenger {
|
|
communityManagerOptions := []communities.ManagerOption{
|
|
communities.WithAllowForcingCommunityMembersReevaluation(true),
|
|
}
|
|
extraOptions = append(extraOptions, WithCommunityManagerOptions(communityManagerOptions))
|
|
|
|
messenger := newTestCommunitiesMessenger(&s.Suite, waku, testCommunitiesMessengerConfig{
|
|
testMessengerConfig: testMessengerConfig{
|
|
logger: s.logger.Named(name),
|
|
extraOptions: extraOptions,
|
|
},
|
|
password: password,
|
|
walletAddresses: walletAddresses,
|
|
mockedBalances: &s.mockedBalances,
|
|
collectiblesService: s.collectiblesServiceMock,
|
|
collectiblesManager: s.collectiblesManagerMock,
|
|
})
|
|
|
|
publicKey := messenger.IdentityPublicKeyString()
|
|
s.accountsTestData[publicKey] = walletAddresses
|
|
s.accountsPasswords[publicKey] = password
|
|
|
|
return messenger
|
|
}
|
|
|
|
func (s *MessengerCommunitiesTokenPermissionsSuite) createRequestToJoinCommunity(communityID types.HexBytes, user *Messenger) *requests.RequestToJoinCommunity {
|
|
userPk := user.IdentityPublicKeyString()
|
|
addresses, exists := s.accountsTestData[userPk]
|
|
s.Require().True(exists)
|
|
password, exists := s.accountsPasswords[userPk]
|
|
s.Require().True(exists)
|
|
return createRequestToJoinCommunity(&s.Suite, communityID, user, password, addresses)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesTokenPermissionsSuite) joinCommunity(community *communities.Community, user *Messenger) {
|
|
addresses, exists := s.accountsTestData[user.IdentityPublicKeyString()]
|
|
s.Require().True(exists)
|
|
password, exists := s.accountsPasswords[user.IdentityPublicKeyString()]
|
|
s.Require().True(exists)
|
|
joinCommunity(&s.Suite, community.ID(), s.owner, user, password, addresses)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesTokenPermissionsSuite) advertiseCommunityTo(community *communities.Community, user *Messenger) {
|
|
advertiseCommunityTo(&s.Suite, community, s.owner, user)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesTokenPermissionsSuite) createCommunity() (*communities.Community, *Chat) {
|
|
return createCommunity(&s.Suite, s.owner)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesTokenPermissionsSuite) sendChatMessage(sender *Messenger, chatID string, text string) *common.Message {
|
|
return sendChatMessage(&s.Suite, sender, chatID, text)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesTokenPermissionsSuite) makeAddressSatisfyTheCriteria(chainID uint64, address string, criteria *protobuf.TokenCriteria) {
|
|
makeAddressSatisfyTheCriteria(&s.Suite, s.mockedBalances, s.mockedCollectibles, chainID, address, criteria)
|
|
}
|
|
|
|
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)
|
|
|
|
s.mockedCollectibles = make(communities.CollectiblesByChain)
|
|
s.mockedCollectibles[testChainID1] = make(map[gethcommon.Address]thirdparty.TokenBalancesPerContractAddress)
|
|
s.mockedCollectibles[testChainID1][gethcommon.HexToAddress(aliceAddress1)] = make(thirdparty.TokenBalancesPerContractAddress)
|
|
s.mockedCollectibles[testChainID1][gethcommon.HexToAddress(aliceAddress2)] = make(thirdparty.TokenBalancesPerContractAddress)
|
|
s.mockedCollectibles[testChainID1][gethcommon.HexToAddress(bobAddress)] = make(thirdparty.TokenBalancesPerContractAddress)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesTokenPermissionsSuite) waitOnKeyDistribution(condition func(*CommunityAndKeyActions) bool) <-chan error {
|
|
testCommunitiesKeyDistributor, ok := s.owner.communitiesKeyDistributor.(*TestCommunitiesKeyDistributor)
|
|
s.Require().True(ok)
|
|
return testCommunitiesKeyDistributor.waitOnKeyDistribution(condition)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesTokenPermissionsSuite) TestCreateTokenPermission() {
|
|
|
|
community, _ := s.createCommunity()
|
|
|
|
createTokenPermission := &requests.CreateCommunityTokenPermission{
|
|
CommunityID: community.ID(),
|
|
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
|
|
TokenCriteria: []*protobuf.TokenCriteria{
|
|
&protobuf.TokenCriteria{
|
|
Type: protobuf.CommunityTokenType_ERC20,
|
|
ContractAddresses: map[uint64]string{uint64(testChainID1): "0x123"},
|
|
Symbol: "TEST",
|
|
AmountInWei: "100000000000000000000",
|
|
Decimals: uint64(18),
|
|
},
|
|
},
|
|
}
|
|
|
|
response, err := s.owner.CreateCommunityTokenPermission(createTokenPermission)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.Communities(), 1)
|
|
|
|
tokenPermissions := response.Communities()[0].TokenPermissions()
|
|
for _, tokenPermission := range tokenPermissions {
|
|
for _, tc := range tokenPermission.TokenCriteria {
|
|
s.Require().Equal(tc.Type, protobuf.CommunityTokenType_ERC20)
|
|
s.Require().Equal(tc.Symbol, "TEST")
|
|
s.Require().Equal(tc.AmountInWei, "100000000000000000000")
|
|
s.Require().Equal(tc.Amount, "100") // automatically upgraded deprecated amount
|
|
s.Require().Equal(tc.Decimals, uint64(18))
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *MessengerCommunitiesTokenPermissionsSuite) TestEditTokenPermission() {
|
|
|
|
community, _ := s.createCommunity()
|
|
|
|
tokenPermission := &requests.CreateCommunityTokenPermission{
|
|
CommunityID: community.ID(),
|
|
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
|
|
TokenCriteria: []*protobuf.TokenCriteria{
|
|
&protobuf.TokenCriteria{
|
|
Type: protobuf.CommunityTokenType_ERC20,
|
|
ContractAddresses: map[uint64]string{testChainID1: "0x123"},
|
|
Symbol: "TEST",
|
|
AmountInWei: "100000000000000000000",
|
|
Decimals: uint64(18),
|
|
},
|
|
},
|
|
}
|
|
|
|
response, err := s.owner.CreateCommunityTokenPermission(tokenPermission)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.Communities(), 1)
|
|
|
|
tokenPermissions := response.Communities()[0].TokenPermissions()
|
|
|
|
var tokenPermissionID string
|
|
for id := range tokenPermissions {
|
|
tokenPermissionID = id
|
|
}
|
|
|
|
tokenPermission.TokenCriteria[0].Symbol = "TESTUpdated"
|
|
tokenPermission.TokenCriteria[0].AmountInWei = "200000000000000000000"
|
|
tokenPermission.TokenCriteria[0].Decimals = uint64(20)
|
|
|
|
editTokenPermission := &requests.EditCommunityTokenPermission{
|
|
PermissionID: tokenPermissionID,
|
|
CreateCommunityTokenPermission: *tokenPermission,
|
|
}
|
|
|
|
response2, err := s.owner.EditCommunityTokenPermission(editTokenPermission)
|
|
s.Require().NoError(err)
|
|
// wait for `checkMemberPermissions` to finish
|
|
time.Sleep(1 * time.Second)
|
|
s.Require().NotNil(response2)
|
|
s.Require().Len(response2.Communities(), 1)
|
|
|
|
tokenPermissions = response2.Communities()[0].TokenPermissions()
|
|
for _, tokenPermission := range tokenPermissions {
|
|
for _, tc := range tokenPermission.TokenCriteria {
|
|
s.Require().Equal(tc.Type, protobuf.CommunityTokenType_ERC20)
|
|
s.Require().Equal(tc.Symbol, "TESTUpdated")
|
|
s.Require().Equal(tc.AmountInWei, "200000000000000000000")
|
|
s.Require().Equal(tc.Amount, "2") // automatically upgraded deprecated amount
|
|
s.Require().Equal(tc.Decimals, uint64(20))
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *MessengerCommunitiesTokenPermissionsSuite) TestCommunityTokensMetadata() {
|
|
|
|
community, _ := s.createCommunity()
|
|
|
|
tokensMetadata := community.CommunityTokensMetadata()
|
|
s.Require().Len(tokensMetadata, 0)
|
|
|
|
newToken := &protobuf.CommunityTokenMetadata{
|
|
ContractAddresses: map[uint64]string{testChainID1: "0xasd"},
|
|
Description: "desc1",
|
|
Image: "IMG1",
|
|
TokenType: protobuf.CommunityTokenType_ERC721,
|
|
Symbol: "SMB",
|
|
Decimals: 3,
|
|
Version: "1.0.0",
|
|
}
|
|
|
|
_, err := community.AddCommunityTokensMetadata(newToken)
|
|
s.Require().NoError(err)
|
|
tokensMetadata = community.CommunityTokensMetadata()
|
|
s.Require().Len(tokensMetadata, 1)
|
|
|
|
s.Require().Equal(tokensMetadata[0].ContractAddresses, newToken.ContractAddresses)
|
|
s.Require().Equal(tokensMetadata[0].Description, newToken.Description)
|
|
s.Require().Equal(tokensMetadata[0].Image, newToken.Image)
|
|
s.Require().Equal(tokensMetadata[0].TokenType, newToken.TokenType)
|
|
s.Require().Equal(tokensMetadata[0].Symbol, newToken.Symbol)
|
|
s.Require().Equal(tokensMetadata[0].Name, newToken.Name)
|
|
s.Require().Equal(tokensMetadata[0].Decimals, newToken.Decimals)
|
|
s.Require().Equal(tokensMetadata[0].Version, newToken.Version)
|
|
}
|
|
|
|
// Note: (mprakhov) after providing revealed addresses this test must be fixed
|
|
func (s *MessengerCommunitiesTokenPermissionsSuite) TestRequestAccessWithENSTokenPermission() {
|
|
s.T().Skip("flaky test")
|
|
community, _ := createCommunity(&s.Suite, s.owner)
|
|
|
|
createTokenPermission := &requests.CreateCommunityTokenPermission{
|
|
CommunityID: community.ID(),
|
|
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
|
|
TokenCriteria: []*protobuf.TokenCriteria{
|
|
&protobuf.TokenCriteria{
|
|
Type: protobuf.CommunityTokenType_ENS,
|
|
EnsPattern: "test.stateofus.eth",
|
|
},
|
|
},
|
|
}
|
|
|
|
response, err := s.owner.CreateCommunityTokenPermission(createTokenPermission)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.Communities(), 1)
|
|
|
|
s.advertiseCommunityTo(community, s.alice)
|
|
|
|
// Make sure declined requests are 0
|
|
declinedRequests, err := s.owner.DeclinedRequestsToJoinForCommunity(community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().Len(declinedRequests, 0)
|
|
|
|
requestToJoin := s.createRequestToJoinCommunity(community.ID(), s.alice)
|
|
// We try to join the org
|
|
response, err = s.alice.RequestToJoinCommunity(requestToJoin)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.RequestsToJoinCommunity(), 1)
|
|
|
|
requestToJoin1 := response.RequestsToJoinCommunity()[0]
|
|
s.Require().Equal(communities.RequestToJoinStatePending, requestToJoin1.State)
|
|
|
|
// Retrieve request to join
|
|
err = tt.RetryWithBackOff(func() error {
|
|
_, err = s.owner.RetrieveAll()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
declinedRequests, err := s.owner.DeclinedRequestsToJoinForCommunity(community.ID())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(declinedRequests) != 1 {
|
|
return errors.New("there should be one declined request")
|
|
}
|
|
if !bytes.Equal(requestToJoin1.ID, declinedRequests[0].ID) {
|
|
return errors.New("wrong declined request")
|
|
}
|
|
return nil
|
|
})
|
|
s.Require().NoError(err)
|
|
|
|
// Ensure alice is not a member of the community
|
|
allCommunities, err := s.owner.Communities()
|
|
if bytes.Equal(allCommunities[0].ID(), community.ID()) {
|
|
s.Require().False(allCommunities[0].HasMember(&s.alice.identity.PublicKey))
|
|
}
|
|
}
|
|
|
|
// NOTE(cammellos): Disabling for now as flaky, for some reason does not pass on CI, but passes locally
|
|
func (s *MessengerCommunitiesTokenPermissionsSuite) TestBecomeMemberPermissions() {
|
|
s.T().Skip("flaky test")
|
|
|
|
// Create a store node
|
|
// This is needed to fetch the messages after rejoining the community
|
|
var err error
|
|
|
|
cfg := testWakuV2Config{
|
|
logger: s.logger.Named("store-node-waku"),
|
|
enableStore: false,
|
|
clusterID: shard.MainStatusShardCluster,
|
|
}
|
|
wakuStoreNode := NewTestWakuV2(&s.Suite, cfg)
|
|
|
|
storeNodeListenAddresses := wakuStoreNode.ListenAddresses()
|
|
s.Require().LessOrEqual(1, len(storeNodeListenAddresses))
|
|
|
|
storeNodeAddress := storeNodeListenAddresses[0]
|
|
s.logger.Info("store node ready", zap.String("address", storeNodeAddress))
|
|
|
|
// Create messengers
|
|
|
|
wakuNodes := CreateWakuV2Network(&s.Suite, s.logger, []string{"owner", "bob"})
|
|
s.ownerWaku = wakuNodes[0]
|
|
s.bobWaku = wakuNodes[1]
|
|
|
|
options := []Option{
|
|
WithTestStoreNode(&s.Suite, localMailserverID, storeNodeAddress, localFleet, s.collectiblesServiceMock),
|
|
}
|
|
|
|
s.owner = s.newMessenger(ownerPassword, []string{ownerAddress}, s.ownerWaku, "owner", options)
|
|
s.Require().NoError(err)
|
|
|
|
_, err = s.owner.Start()
|
|
s.Require().NoError(err)
|
|
|
|
s.bob = s.newMessenger(bobPassword, []string{bobAddress}, s.bobWaku, "bob", options)
|
|
s.Require().NoError(err)
|
|
|
|
_, err = s.bob.Start()
|
|
s.Require().NoError(err)
|
|
|
|
// Force the owner to use the store node as relay peer
|
|
|
|
err = s.owner.DialPeer(storeNodeAddress)
|
|
s.Require().NoError(err)
|
|
|
|
// Create a community
|
|
|
|
community, chat := s.createCommunity()
|
|
|
|
// bob joins the community
|
|
s.advertiseCommunityTo(community, s.bob)
|
|
s.joinCommunity(community, s.bob)
|
|
|
|
messages := []string{
|
|
"1-message", // RandomLettersString(10), // successful message on open community
|
|
"2-message", // RandomLettersString(11), // failing message on encrypted community
|
|
"3-message", // RandomLettersString(12), // successful message on encrypted community
|
|
}
|
|
|
|
// send message to the channel
|
|
msg := s.sendChatMessage(s.owner, chat.ID, messages[0])
|
|
s.logger.Debug("owner sent a message",
|
|
zap.String("messageText", msg.Text),
|
|
zap.String("messageID", msg.ID),
|
|
)
|
|
|
|
// bob can read the message
|
|
response, err := WaitOnMessengerResponse(
|
|
s.bob,
|
|
func(r *MessengerResponse) bool {
|
|
for _, message := range r.messages {
|
|
if message.Text == msg.Text {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
},
|
|
"first message not received",
|
|
)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Messages(), 1)
|
|
s.Require().Equal(msg.Text, response.Messages()[0].Text)
|
|
|
|
bobMessages, _, err := s.bob.MessageByChatID(msg.ChatId, "", 10)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(bobMessages, 1)
|
|
s.Require().Equal(messages[0], bobMessages[0].Text)
|
|
|
|
// setup become member permission
|
|
permissionRequest := requests.CreateCommunityTokenPermission{
|
|
CommunityID: community.ID(),
|
|
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
|
|
TokenCriteria: []*protobuf.TokenCriteria{
|
|
&protobuf.TokenCriteria{
|
|
Type: protobuf.CommunityTokenType_ERC20,
|
|
ContractAddresses: map[uint64]string{testChainID1: "0x123"},
|
|
Symbol: "TEST",
|
|
Amount: "100",
|
|
Decimals: uint64(18),
|
|
},
|
|
},
|
|
}
|
|
|
|
waitOnBobToBeKicked := waitOnCommunitiesEvent(s.owner, func(sub *communities.Subscription) bool {
|
|
return len(sub.Community.Members()) == 1
|
|
})
|
|
|
|
response, err = s.owner.CreateCommunityTokenPermission(&permissionRequest)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Communities(), 1)
|
|
|
|
err = <-waitOnBobToBeKicked
|
|
s.Require().NoError(err)
|
|
|
|
// bob should be kicked from the community,
|
|
// because he doesn't meet the criteria
|
|
community, err = s.owner.communitiesManager.GetByID(community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().Len(community.Members(), 1)
|
|
|
|
// bob receives community changes
|
|
// chats and members should be empty,
|
|
// this info is available only to members
|
|
_, err = WaitOnMessengerResponse(
|
|
s.bob,
|
|
func(r *MessengerResponse) bool {
|
|
return len(r.Communities()) == 1 && len(community.TokenPermissions()) > 0 && r.Communities()[0].IDString() == community.IDString() && !r.Communities()[0].Joined()
|
|
},
|
|
"no community that satisfies criteria",
|
|
)
|
|
s.Require().NoError(err)
|
|
|
|
// We are not member of the community anymore, so we need to refetch
|
|
// the data, since we would not be pulling it anymore
|
|
_, err = WaitOnMessengerResponse(
|
|
s.bob,
|
|
func(r *MessengerResponse) bool {
|
|
_, err := s.bob.FetchCommunity(&FetchCommunityRequest{WaitForResponse: true, TryDatabase: false, CommunityKey: community.IDString()})
|
|
if err != nil {
|
|
return false
|
|
}
|
|
c, err := s.bob.communitiesManager.GetByID(community.ID())
|
|
return err == nil && c != nil && len(c.TokenPermissions()) > 0 && !c.Joined()
|
|
},
|
|
"no token permissions",
|
|
)
|
|
|
|
s.Require().NoError(err)
|
|
|
|
// bob tries to join, but he doesn't satisfy so the request isn't sent
|
|
request := s.createRequestToJoinCommunity(community.ID(), s.bob)
|
|
_, err = s.bob.RequestToJoinCommunity(request)
|
|
s.Require().ErrorIs(err, communities.ErrPermissionToJoinNotSatisfied)
|
|
|
|
// make sure bob does not have a pending request to join
|
|
pendingRequests, err := s.bob.MyPendingRequestsToJoin()
|
|
s.Require().NoError(err)
|
|
s.Require().Len(pendingRequests, 0)
|
|
|
|
// Send chat message while bob is not in the community
|
|
msg = s.sendChatMessage(s.owner, chat.ID, messages[1])
|
|
|
|
// make bob satisfy the criteria
|
|
s.makeAddressSatisfyTheCriteria(testChainID1, bobAddress, permissionRequest.TokenCriteria[0])
|
|
|
|
waitOnCommunityKeyToBeDistributedToBob := s.waitOnKeyDistribution(func(sub *CommunityAndKeyActions) bool {
|
|
return len(sub.community.Description().Members) == 2 &&
|
|
len(sub.keyActions.CommunityKeyAction.Members) == 1 &&
|
|
sub.keyActions.CommunityKeyAction.ActionType == communities.EncryptionKeySendToMembers
|
|
})
|
|
|
|
// bob re-joins the community
|
|
s.joinCommunity(community, s.bob)
|
|
|
|
err = <-waitOnCommunityKeyToBeDistributedToBob
|
|
s.Require().NoError(err)
|
|
|
|
// send message to channel
|
|
msg = s.sendChatMessage(s.owner, chat.ID, messages[2])
|
|
s.logger.Debug("owner sent a message",
|
|
zap.String("messageText", msg.Text),
|
|
zap.String("messageID", msg.ID),
|
|
)
|
|
|
|
// bob can read the message
|
|
_, err = WaitOnMessengerResponse(
|
|
s.bob,
|
|
func(r *MessengerResponse) bool {
|
|
// Bob should have all 3 messages
|
|
bobMessages, _, err = s.bob.MessageByChatID(msg.ChatId, "", 10)
|
|
return err == nil && len(bobMessages) == 3
|
|
},
|
|
"not all 3 messages received",
|
|
)
|
|
bobMessages, _, err = s.bob.MessageByChatID(msg.ChatId, "", 10)
|
|
for _, m := range bobMessages {
|
|
fmt.Printf("ID: %s\n", m.ID)
|
|
}
|
|
s.Require().NoError(err)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesTokenPermissionsSuite) TestJoinCommunityWithAdminPermission() {
|
|
|
|
community, _ := s.createCommunity()
|
|
|
|
// setup become admin permission
|
|
permissionRequest := requests.CreateCommunityTokenPermission{
|
|
CommunityID: community.ID(),
|
|
Type: protobuf.CommunityTokenPermission_BECOME_ADMIN,
|
|
TokenCriteria: []*protobuf.TokenCriteria{
|
|
&protobuf.TokenCriteria{
|
|
Type: protobuf.CommunityTokenType_ERC20,
|
|
ContractAddresses: map[uint64]string{testChainID1: "0x123"},
|
|
Symbol: "TEST",
|
|
AmountInWei: "100000000000000000000",
|
|
Decimals: uint64(18),
|
|
},
|
|
},
|
|
}
|
|
|
|
response, err := s.owner.CreateCommunityTokenPermission(&permissionRequest)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Communities(), 1)
|
|
|
|
s.advertiseCommunityTo(community, s.bob)
|
|
|
|
// Bob should still be able to join even if there is a permission to be an admin
|
|
s.joinCommunity(community, s.bob)
|
|
|
|
// Verify that we have Bob's revealed account
|
|
revealedAccounts, err := s.owner.GetRevealedAccounts(community.ID(), common.PubkeyToHex(&s.bob.identity.PublicKey))
|
|
s.Require().NoError(err)
|
|
s.Require().Len(revealedAccounts, 1)
|
|
s.Require().Equal(bobAddress, revealedAccounts[0].Address)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesTokenPermissionsSuite) TestJoinCommunityAsMemberWithMemberAndAdminPermission() {
|
|
community, _ := s.createCommunity()
|
|
|
|
waitOnCommunityPermissionCreated := waitOnCommunitiesEvent(s.owner, func(sub *communities.Subscription) bool {
|
|
return sub.Community.HasTokenPermissions()
|
|
})
|
|
|
|
// setup become member permission
|
|
permissionRequestMember := requests.CreateCommunityTokenPermission{
|
|
CommunityID: community.ID(),
|
|
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
|
|
TokenCriteria: []*protobuf.TokenCriteria{
|
|
&protobuf.TokenCriteria{
|
|
Type: protobuf.CommunityTokenType_ERC20,
|
|
ContractAddresses: map[uint64]string{testChainID1: "0x123"},
|
|
Symbol: "TEST",
|
|
AmountInWei: "100000000000000000000",
|
|
Decimals: uint64(18),
|
|
},
|
|
},
|
|
}
|
|
response, err := s.owner.CreateCommunityTokenPermission(&permissionRequestMember)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Communities(), 1)
|
|
|
|
err = <-waitOnCommunityPermissionCreated
|
|
s.Require().NoError(err)
|
|
|
|
// setup become admin permission
|
|
permissionRequestAdmin := requests.CreateCommunityTokenPermission{
|
|
CommunityID: community.ID(),
|
|
Type: protobuf.CommunityTokenPermission_BECOME_ADMIN,
|
|
TokenCriteria: []*protobuf.TokenCriteria{
|
|
&protobuf.TokenCriteria{
|
|
Type: protobuf.CommunityTokenType_ERC20,
|
|
ContractAddresses: map[uint64]string{testChainID1: "0x124"},
|
|
Symbol: "TESTADMIN",
|
|
AmountInWei: "100000000000000000000",
|
|
Decimals: uint64(18),
|
|
},
|
|
},
|
|
}
|
|
|
|
waitOnCommunityPermissionCreated = waitOnCommunitiesEvent(s.owner, func(sub *communities.Subscription) bool {
|
|
return len(sub.Community.TokenPermissions()) == 2
|
|
})
|
|
|
|
response, err = s.owner.CreateCommunityTokenPermission(&permissionRequestAdmin)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Communities(), 1)
|
|
|
|
err = <-waitOnCommunityPermissionCreated
|
|
s.Require().NoError(err)
|
|
|
|
// make bob satisfy the member criteria
|
|
s.makeAddressSatisfyTheCriteria(testChainID1, bobAddress, permissionRequestMember.TokenCriteria[0])
|
|
|
|
s.advertiseCommunityTo(response.Communities()[0], s.bob)
|
|
|
|
// Bob should still be able to join even though he doesn't satisfy the admin requirement
|
|
// because he satisfies the member one
|
|
s.joinCommunity(community, s.bob)
|
|
|
|
// Verify that we have Bob's revealed account
|
|
revealedAccounts, err := s.owner.GetRevealedAccounts(community.ID(), common.PubkeyToHex(&s.bob.identity.PublicKey))
|
|
s.Require().NoError(err)
|
|
s.Require().Len(revealedAccounts, 1)
|
|
s.Require().Equal(bobAddress, revealedAccounts[0].Address)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesTokenPermissionsSuite) TestJoinCommunityAsAdminWithMemberAndAdminPermission() {
|
|
|
|
community, _ := s.createCommunity()
|
|
|
|
// setup become member permission
|
|
permissionRequestMember := requests.CreateCommunityTokenPermission{
|
|
CommunityID: community.ID(),
|
|
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
|
|
TokenCriteria: []*protobuf.TokenCriteria{
|
|
&protobuf.TokenCriteria{
|
|
Type: protobuf.CommunityTokenType_ERC20,
|
|
ContractAddresses: map[uint64]string{testChainID1: "0x123"},
|
|
Symbol: "TEST",
|
|
AmountInWei: "100000000000000000000",
|
|
Decimals: uint64(18),
|
|
},
|
|
},
|
|
}
|
|
|
|
waitOnCommunityPermissionCreated := waitOnCommunitiesEvent(s.owner, func(sub *communities.Subscription) bool {
|
|
return sub.Community.HasTokenPermissions()
|
|
})
|
|
|
|
response, err := s.owner.CreateCommunityTokenPermission(&permissionRequestMember)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Communities(), 1)
|
|
|
|
err = <-waitOnCommunityPermissionCreated
|
|
s.Require().NoError(err)
|
|
|
|
// setup become admin permission
|
|
permissionRequestAdmin := requests.CreateCommunityTokenPermission{
|
|
CommunityID: community.ID(),
|
|
Type: protobuf.CommunityTokenPermission_BECOME_ADMIN,
|
|
TokenCriteria: []*protobuf.TokenCriteria{
|
|
&protobuf.TokenCriteria{
|
|
Type: protobuf.CommunityTokenType_ERC20,
|
|
ContractAddresses: map[uint64]string{testChainID1: "0x124"},
|
|
Symbol: "TESTADMIN",
|
|
AmountInWei: "100000000000000000000",
|
|
Decimals: uint64(18),
|
|
},
|
|
},
|
|
}
|
|
|
|
waitOnCommunityPermissionCreated = waitOnCommunitiesEvent(s.owner, func(sub *communities.Subscription) bool {
|
|
return len(sub.Community.TokenPermissions()) == 2
|
|
})
|
|
|
|
response, err = s.owner.CreateCommunityTokenPermission(&permissionRequestAdmin)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Communities(), 1)
|
|
s.Require().Len(response.Communities()[0].TokenPermissionsByType(protobuf.CommunityTokenPermission_BECOME_ADMIN), 1)
|
|
s.Require().Len(response.Communities()[0].TokenPermissions(), 2)
|
|
|
|
err = <-waitOnCommunityPermissionCreated
|
|
s.Require().NoError(err)
|
|
|
|
community, err = s.owner.communitiesManager.GetByID(community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().Len(community.TokenPermissions(), 2)
|
|
|
|
s.advertiseCommunityTo(community, s.bob)
|
|
|
|
// make bob satisfy the admin criteria
|
|
s.makeAddressSatisfyTheCriteria(testChainID1, bobAddress, permissionRequestAdmin.TokenCriteria[0])
|
|
|
|
// Bob should still be able to join even though he doesn't satisfy the member requirement
|
|
// because he satisfies the admin one
|
|
s.joinCommunity(community, s.bob)
|
|
|
|
// Verify that we have Bob's revealed account
|
|
revealedAccounts, err := s.owner.GetRevealedAccounts(community.ID(), common.PubkeyToHex(&s.bob.identity.PublicKey))
|
|
s.Require().NoError(err)
|
|
s.Require().Len(revealedAccounts, 1)
|
|
s.Require().Equal(bobAddress, revealedAccounts[0].Address)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesTokenPermissionsSuite) testViewChannelPermissions(viewersCanAddReactions bool) {
|
|
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
|
|
s.advertiseCommunityTo(community, s.bob)
|
|
s.joinCommunity(community, s.bob)
|
|
|
|
// send message to the channel
|
|
msg := s.sendChatMessage(s.owner, chat.ID, "hello on open community")
|
|
|
|
// bob can read the message
|
|
response, err := WaitOnMessengerResponse(
|
|
s.bob,
|
|
func(r *MessengerResponse) bool {
|
|
_, ok := r.messages[msg.ID]
|
|
return ok
|
|
},
|
|
"no messages",
|
|
)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Messages(), 1)
|
|
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
|
|
channelPermissionRequest := requests.CreateCommunityTokenPermission{
|
|
CommunityID: community.ID(),
|
|
Type: protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL,
|
|
TokenCriteria: []*protobuf.TokenCriteria{
|
|
&protobuf.TokenCriteria{
|
|
Type: protobuf.CommunityTokenType_ERC20,
|
|
ContractAddresses: map[uint64]string{testChainID1: "0x123"},
|
|
Symbol: "TEST",
|
|
AmountInWei: "100000000000000000000",
|
|
Decimals: uint64(18),
|
|
},
|
|
},
|
|
ChatIds: []string{chat.ID},
|
|
}
|
|
|
|
response, err = s.owner.CreateCommunityTokenPermission(&channelPermissionRequest)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Communities(), 1)
|
|
s.Require().True(s.owner.communitiesManager.IsChannelEncrypted(community.IDString(), chat.ID))
|
|
|
|
err = <-waitOnBobToBeKickedFromChannel
|
|
s.Require().NoError(err)
|
|
|
|
err = <-waitOnChannelToBeRekeyedOnceBobIsKicked
|
|
s.Require().NoError(err)
|
|
|
|
// bob receives community changes
|
|
// channel members should be empty,
|
|
// this info is available only to channel members
|
|
_, err = WaitOnMessengerResponse(
|
|
s.bob,
|
|
func(r *MessengerResponse) bool {
|
|
c, err := s.bob.GetCommunityByID(community.ID())
|
|
if err != nil {
|
|
return false
|
|
}
|
|
if c == nil {
|
|
return false
|
|
}
|
|
channel := c.Chats()[chat.CommunityChatID()]
|
|
return channel != nil && len(channel.Members) == 0
|
|
},
|
|
"no community that satisfies criteria",
|
|
)
|
|
s.Require().NoError(err)
|
|
|
|
// bob should not be in the bloom filter list
|
|
community, err = s.bob.communitiesManager.GetByID(community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().False(community.IsMemberLikelyInChat(chat.CommunityChatID()))
|
|
|
|
// make bob satisfy channel criteria
|
|
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 {
|
|
action, ok := sub.keyActions.ChannelKeysActions[chat.CommunityChatID()]
|
|
if !ok || action.ActionType != communities.EncryptionKeySendToMembers {
|
|
return false
|
|
}
|
|
_, ok = action.Members[common.PubkeyToHex(&s.bob.identity.PublicKey)]
|
|
return ok
|
|
})
|
|
|
|
// force owner to reevaluate channel members
|
|
// in production it will happen automatically, by periodic check
|
|
err = s.owner.communitiesManager.ForceMembersReevaluation(community.ID())
|
|
s.Require().NoError(err)
|
|
|
|
err = <-waitOnChannelKeyToBeDistributedToBob
|
|
s.Require().NoError(err)
|
|
|
|
// send message to the channel
|
|
msg = s.sendChatMessage(s.owner, chat.ID, "hello on closed channel")
|
|
|
|
// bob can read the message
|
|
response, err = WaitOnMessengerResponse(
|
|
s.bob,
|
|
func(r *MessengerResponse) bool {
|
|
_, ok := r.messages[msg.ID]
|
|
return ok
|
|
},
|
|
"no messages",
|
|
)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Messages(), 1)
|
|
s.Require().Equal(msg.Text, response.Messages()[0].Text)
|
|
|
|
// bob should be in the bloom filter list
|
|
community, err = s.bob.communitiesManager.GetByID(community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().True(community.IsMemberLikelyInChat(chat.CommunityChatID()))
|
|
|
|
// 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) TestAnnouncementsChannelPermissions() {
|
|
community, chat := s.createCommunity()
|
|
|
|
// bob joins the community
|
|
s.advertiseCommunityTo(community, s.bob)
|
|
s.joinCommunity(community, s.bob)
|
|
|
|
// setup view channel permission
|
|
channelPermissionRequest := requests.CreateCommunityTokenPermission{
|
|
CommunityID: community.ID(),
|
|
Type: protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL,
|
|
ChatIds: []string{chat.ID},
|
|
}
|
|
|
|
response, err := s.owner.CreateCommunityTokenPermission(&channelPermissionRequest)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Communities(), 1)
|
|
s.Require().False(s.owner.communitiesManager.IsChannelEncrypted(community.IDString(), chat.ID))
|
|
|
|
// bob should be in the bloom filter list since everyone has access to readonly channels
|
|
community, err = s.bob.communitiesManager.GetByID(community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().True(community.IsMemberLikelyInChat(chat.CommunityChatID()))
|
|
|
|
// force owner to reevaluate channel members
|
|
// in production it will happen automatically, by periodic check
|
|
err = s.owner.communitiesManager.ForceMembersReevaluation(community.ID())
|
|
s.Require().NoError(err)
|
|
|
|
// bob receives community changes
|
|
_, err = WaitOnMessengerResponse(
|
|
s.bob,
|
|
func(r *MessengerResponse) bool {
|
|
c, err := s.bob.GetCommunityByID(community.ID())
|
|
if err != nil {
|
|
return false
|
|
}
|
|
if c == nil {
|
|
return false
|
|
}
|
|
channel := c.Chats()[chat.CommunityChatID()]
|
|
|
|
if channel == nil || len(channel.Members) != 2 {
|
|
return false
|
|
}
|
|
member := channel.Members[s.bob.IdentityPublicKeyString()]
|
|
return member != nil && member.ChannelRole == protobuf.CommunityMember_CHANNEL_ROLE_VIEWER
|
|
},
|
|
"no community that satisfies criteria",
|
|
)
|
|
s.Require().NoError(err)
|
|
|
|
// bob should be in the bloom filter list
|
|
community, err = s.bob.communitiesManager.GetByID(community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().True(community.IsMemberLikelyInChat(chat.CommunityChatID()))
|
|
|
|
// bob can't post
|
|
msg := &common.Message{
|
|
ChatMessage: &protobuf.ChatMessage{
|
|
ChatId: chat.ID,
|
|
ContentType: protobuf.ChatMessage_TEXT_PLAIN,
|
|
Text: "I can't post on read-only channel",
|
|
},
|
|
}
|
|
|
|
_, err = s.bob.SendChatMessage(context.Background(), msg)
|
|
s.Require().Error(err)
|
|
s.Require().Contains(err.Error(), "can't post")
|
|
}
|
|
|
|
func (s *MessengerCommunitiesTokenPermissionsSuite) TestSearchMessageinPermissionedChannel() {
|
|
community, chat := s.createCommunity()
|
|
|
|
newChat := protobuf.CommunityChat{
|
|
Permissions: &protobuf.CommunityPermissions{
|
|
EnsOnly: false,
|
|
Private: false,
|
|
Access: 1,
|
|
},
|
|
Identity: &protobuf.ChatIdentity{
|
|
DisplayName: "new-channel",
|
|
Description: "description",
|
|
Emoji: "",
|
|
Color: "",
|
|
},
|
|
CategoryId: "",
|
|
ViewersCanPostReactions: true,
|
|
HideIfPermissionsNotMet: false,
|
|
}
|
|
|
|
response, err := s.owner.CreateCommunityChat(community.ID(), &newChat)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
newChatID := response.Chats()[0].ID
|
|
|
|
// bob joins the community
|
|
s.advertiseCommunityTo(community, s.bob)
|
|
s.joinCommunity(community, s.bob)
|
|
|
|
// send message to the original channel
|
|
msg := s.sendChatMessage(s.owner, chat.ID, "hello on open community")
|
|
|
|
// bob can read the message
|
|
response, err = WaitOnMessengerResponse(
|
|
s.bob,
|
|
func(r *MessengerResponse) bool {
|
|
_, ok := r.messages[msg.ID]
|
|
return ok
|
|
},
|
|
"no messages",
|
|
)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Messages(), 1)
|
|
s.Require().Equal(msg.Text, response.Messages()[0].Text)
|
|
|
|
// send message to the new channel
|
|
msgText := "hello on new chat"
|
|
msg = s.sendChatMessage(s.owner, newChatID, msgText)
|
|
|
|
// bob can read the message
|
|
response, err = WaitOnMessengerResponse(
|
|
s.bob,
|
|
func(r *MessengerResponse) bool {
|
|
_, ok := r.messages[msg.ID]
|
|
return ok
|
|
},
|
|
"no messages",
|
|
)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Messages(), 1)
|
|
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
|
|
channelPermissionRequest := requests.CreateCommunityTokenPermission{
|
|
CommunityID: community.ID(),
|
|
Type: protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL,
|
|
TokenCriteria: []*protobuf.TokenCriteria{
|
|
&protobuf.TokenCriteria{
|
|
Type: protobuf.CommunityTokenType_ERC20,
|
|
ContractAddresses: map[uint64]string{testChainID1: "0x123"},
|
|
Symbol: "TEST",
|
|
AmountInWei: "100000000000000000000",
|
|
Decimals: uint64(18),
|
|
},
|
|
},
|
|
ChatIds: []string{chat.ID},
|
|
}
|
|
|
|
response, err = s.owner.CreateCommunityTokenPermission(&channelPermissionRequest)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Communities(), 1)
|
|
s.Require().True(s.owner.communitiesManager.IsChannelEncrypted(community.IDString(), chat.ID))
|
|
|
|
err = <-waitOnBobToBeKickedFromChannel
|
|
s.Require().NoError(err)
|
|
|
|
err = <-waitOnChannelToBeRekeyedOnceBobIsKicked
|
|
s.Require().NoError(err)
|
|
|
|
// bob receives community changes
|
|
// channel members should be empty,
|
|
// this info is available only to channel members
|
|
_, err = WaitOnMessengerResponse(
|
|
s.bob,
|
|
func(r *MessengerResponse) bool {
|
|
c, err := s.bob.GetCommunityByID(community.ID())
|
|
if err != nil {
|
|
return false
|
|
}
|
|
if c == nil {
|
|
return false
|
|
}
|
|
channel := c.Chats()[chat.CommunityChatID()]
|
|
return channel != nil && len(channel.Members) == 0
|
|
},
|
|
"no community that satisfies criteria",
|
|
)
|
|
s.Require().NoError(err)
|
|
|
|
// Bob searches for "hello" but only finds it in the new channel
|
|
communities := make([]string, 1)
|
|
communities[0] = community.IDString()
|
|
messages, err := s.bob.AllMessagesFromChatsAndCommunitiesWhichMatchTerm(communities, make([]string, 0), "hello", false)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(messages, 1)
|
|
s.Require().Equal(msgText, messages[0].Text)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesTokenPermissionsSuite) TestMemberRoleGetUpdatedWhenChangingPermissions() {
|
|
community, chat := s.createCommunity()
|
|
|
|
// bob joins the community
|
|
s.advertiseCommunityTo(community, s.bob)
|
|
s.joinCommunity(community, s.bob)
|
|
|
|
community, err := s.owner.communitiesManager.GetByID(community.ID())
|
|
s.Require().NoError(err)
|
|
|
|
// send message to the channel
|
|
msg := s.sendChatMessage(s.owner, chat.ID, "hello on open community")
|
|
|
|
// bob can read the message
|
|
response, err := WaitOnMessengerResponse(
|
|
s.bob,
|
|
func(r *MessengerResponse) bool {
|
|
_, ok := r.messages[msg.ID]
|
|
return ok
|
|
},
|
|
"no messages",
|
|
)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Messages(), 1)
|
|
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
|
|
channelPermissionRequest := requests.CreateCommunityTokenPermission{
|
|
CommunityID: community.ID(),
|
|
Type: protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL,
|
|
TokenCriteria: []*protobuf.TokenCriteria{
|
|
&protobuf.TokenCriteria{
|
|
Type: protobuf.CommunityTokenType_ERC20,
|
|
ContractAddresses: map[uint64]string{testChainID1: "0x123"},
|
|
Symbol: "TEST",
|
|
AmountInWei: "100000000000000000000",
|
|
Decimals: uint64(18),
|
|
},
|
|
},
|
|
ChatIds: []string{chat.ID},
|
|
}
|
|
|
|
response, err = s.owner.CreateCommunityTokenPermission(&channelPermissionRequest)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Communities(), 1)
|
|
s.Require().Len(response.CommunityChanges[0].TokenPermissionsAdded, 1)
|
|
s.Require().True(s.owner.communitiesManager.IsChannelEncrypted(community.IDString(), chat.ID))
|
|
|
|
err = <-waitOnBobToBeKickedFromChannel
|
|
s.Require().NoError(err)
|
|
|
|
err = <-waitOnChannelToBeRekeyedOnceBobIsKicked
|
|
s.Require().NoError(err)
|
|
|
|
// bob receives community changes
|
|
// channel members should be empty,
|
|
// this info is available only to channel members
|
|
_, err = WaitOnMessengerResponse(
|
|
s.bob,
|
|
func(r *MessengerResponse) bool {
|
|
c, err := s.bob.GetCommunityByID(community.ID())
|
|
if err != nil {
|
|
return false
|
|
}
|
|
if c == nil {
|
|
return false
|
|
}
|
|
channel := c.Chats()[chat.CommunityChatID()]
|
|
return channel != nil && len(channel.Members) == 0
|
|
},
|
|
"no community that satisfies criteria",
|
|
)
|
|
s.Require().NoError(err)
|
|
|
|
// make bob satisfy channel criteria
|
|
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 {
|
|
action, ok := sub.keyActions.ChannelKeysActions[chat.CommunityChatID()]
|
|
if !ok || action.ActionType != communities.EncryptionKeySendToMembers {
|
|
return false
|
|
}
|
|
_, ok = action.Members[common.PubkeyToHex(&s.bob.identity.PublicKey)]
|
|
return ok
|
|
})
|
|
|
|
// force owner to reevaluate channel members
|
|
// in production it will happen automatically, by periodic check
|
|
err = s.owner.communitiesManager.ForceMembersReevaluation(community.ID())
|
|
s.Require().NoError(err)
|
|
|
|
err = <-waitOnChannelKeyToBeDistributedToBob
|
|
s.Require().NoError(err)
|
|
|
|
community, err = s.owner.communitiesManager.GetByID(community.ID())
|
|
s.Require().NoError(err)
|
|
chatID := strings.TrimPrefix(chat.ID, community.IDString())
|
|
members := community.Chats()[chatID].Members
|
|
s.Require().Len(members, 2)
|
|
// confirm that member is a viewer and not a poster
|
|
s.Require().Equal(protobuf.CommunityMember_CHANNEL_ROLE_VIEWER, members[s.bob.IdentityPublicKeyString()].ChannelRole)
|
|
|
|
// send message to the channel
|
|
msg = s.sendChatMessage(s.owner, chat.ID, "hello on closed channel")
|
|
|
|
// bob can read the message
|
|
response, err = WaitOnMessengerResponse(
|
|
s.bob,
|
|
func(r *MessengerResponse) bool {
|
|
_, ok := r.messages[msg.ID]
|
|
return ok
|
|
},
|
|
"no messages",
|
|
)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Messages(), 1)
|
|
s.Require().Equal(msg.Text, response.Messages()[0].Text)
|
|
|
|
tokenPermissions := community.TokenPermissions()
|
|
|
|
var tokenPermissionID string
|
|
for id := range tokenPermissions {
|
|
tokenPermissionID = id
|
|
}
|
|
|
|
// Edit permission so that Bob can now be a poster to show that member role can be edited
|
|
channelPermissionRequest.Type = protobuf.CommunityTokenPermission_CAN_VIEW_AND_POST_CHANNEL
|
|
editChannelPermissionRequest := requests.EditCommunityTokenPermission{
|
|
PermissionID: tokenPermissionID,
|
|
CreateCommunityTokenPermission: channelPermissionRequest,
|
|
}
|
|
response, err = s.owner.EditCommunityTokenPermission(&editChannelPermissionRequest)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Communities(), 1)
|
|
s.Require().True(s.owner.communitiesManager.IsChannelEncrypted(community.IDString(), chat.ID))
|
|
s.Require().Len(response.CommunityChanges[0].TokenPermissionsModified, 1)
|
|
|
|
waitOnBobAddedToChannelAsPoster := waitOnCommunitiesEvent(s.owner, func(sub *communities.Subscription) bool {
|
|
channel, ok := sub.Community.Chats()[chat.CommunityChatID()]
|
|
if !ok {
|
|
return false
|
|
}
|
|
member, ok := channel.Members[s.bob.IdentityPublicKeyString()]
|
|
if !ok {
|
|
return false
|
|
}
|
|
return member.ChannelRole == protobuf.CommunityMember_CHANNEL_ROLE_POSTER
|
|
})
|
|
|
|
// force owner to reevaluate channel members
|
|
// in production it will happen automatically, by periodic check
|
|
err = s.owner.communitiesManager.ForceMembersReevaluation(community.ID())
|
|
s.Require().NoError(err)
|
|
|
|
err = <-waitOnBobAddedToChannelAsPoster
|
|
s.Require().NoError(err)
|
|
|
|
community, err = s.owner.communitiesManager.GetByID(community.ID())
|
|
s.Require().NoError(err)
|
|
|
|
members = community.Chats()[chatID].Members
|
|
s.Require().Len(members, 2)
|
|
// confirm that member is now a poster
|
|
s.Require().Equal(protobuf.CommunityMember_CHANNEL_ROLE_POSTER, members[s.bob.IdentityPublicKeyString()].ChannelRole)
|
|
|
|
err = <-waitOnChannelKeyToBeDistributedToBob
|
|
s.Require().NoError(err)
|
|
|
|
// wait until bob permissions are upgraded
|
|
_, err = WaitOnMessengerResponse(
|
|
s.bob,
|
|
func(r *MessengerResponse) bool {
|
|
community, err = s.bob.communitiesManager.GetByID(community.ID())
|
|
s.Require().NoError(err)
|
|
chats := community.Chats()
|
|
if len(chats) == 0 {
|
|
return false
|
|
}
|
|
if chats[chat.ID] == nil {
|
|
return false
|
|
}
|
|
members = chats[chat.ID].Members
|
|
return len(members) == 2 && members[s.bob.myHexIdentity()] != nil && members[s.bob.myHexIdentity()].ChannelRole == protobuf.CommunityMember_CHANNEL_ROLE_POSTER
|
|
},
|
|
"bob never got post permissions",
|
|
)
|
|
|
|
msg = s.sendChatMessage(s.bob, chat.ID, "hello on closed channel from Bob")
|
|
|
|
// owner can read the message
|
|
response, err = WaitOnMessengerResponse(
|
|
s.owner,
|
|
func(r *MessengerResponse) bool {
|
|
_, ok := r.messages[msg.ID]
|
|
return ok
|
|
},
|
|
"no messages",
|
|
)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Messages(), 1)
|
|
s.Require().Equal(msg.Text, response.Messages()[0].Text)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesTokenPermissionsSuite) testReevaluateMemberPrivilegedRoleInOpenCommunity(permissionType protobuf.CommunityTokenPermission_Type, tokenType protobuf.CommunityTokenType) {
|
|
community, _ := s.createCommunity()
|
|
|
|
amountInWei := "100000000000000000000"
|
|
decimals := uint64(18)
|
|
if tokenType == protobuf.CommunityTokenType_ERC721 {
|
|
amountInWei = "1"
|
|
decimals = 0
|
|
}
|
|
|
|
createTokenPermission := &requests.CreateCommunityTokenPermission{
|
|
CommunityID: community.ID(),
|
|
Type: permissionType,
|
|
TokenCriteria: []*protobuf.TokenCriteria{
|
|
{
|
|
Type: tokenType,
|
|
ContractAddresses: map[uint64]string{testChainID1: "0x123"},
|
|
Symbol: "TEST",
|
|
AmountInWei: amountInWei,
|
|
Decimals: decimals,
|
|
},
|
|
},
|
|
}
|
|
|
|
waitOnCommunityPermissionCreated := waitOnCommunitiesEvent(s.owner, func(sub *communities.Subscription) bool {
|
|
return sub.Community.HasTokenPermissions()
|
|
})
|
|
|
|
response, err := s.owner.CreateCommunityTokenPermission(createTokenPermission)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.Communities(), 1)
|
|
s.Require().True(response.Communities()[0].HasTokenPermissions())
|
|
|
|
err = <-waitOnCommunityPermissionCreated
|
|
s.Require().NoError(err)
|
|
|
|
community, err = s.owner.communitiesManager.GetByID(community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().True(community.HasTokenPermissions())
|
|
|
|
s.advertiseCommunityTo(community, s.alice)
|
|
|
|
var tokenPermission *communities.CommunityTokenPermission
|
|
for _, tokenPermission = range community.TokenPermissions() {
|
|
break
|
|
}
|
|
|
|
s.makeAddressSatisfyTheCriteria(testChainID1, aliceAddress1, tokenPermission.TokenCriteria[0])
|
|
|
|
// join community as a privileged user
|
|
s.joinCommunity(community, s.alice)
|
|
|
|
community, err = s.owner.communitiesManager.GetByID(community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().True(checkRoleBasedOnThePermissionType(permissionType, &s.alice.identity.PublicKey, community))
|
|
|
|
waitOnPermissionsReevaluated := waitOnCommunitiesEvent(s.owner, func(sub *communities.Subscription) bool {
|
|
if sub.Community == nil {
|
|
return false
|
|
}
|
|
return checkRoleBasedOnThePermissionType(permissionType, &s.alice.identity.PublicKey, sub.Community)
|
|
})
|
|
|
|
// the control node re-evaluates the roles of the participants, checking that the privileged user has not lost his role
|
|
err = s.owner.communitiesManager.ForceMembersReevaluation(community.ID())
|
|
s.Require().NoError(err)
|
|
|
|
err = <-waitOnPermissionsReevaluated
|
|
s.Require().NoError(err)
|
|
|
|
community, err = s.owner.communitiesManager.GetByID(community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().True(checkRoleBasedOnThePermissionType(permissionType, &s.alice.identity.PublicKey, community))
|
|
|
|
// remove privileged token permission and reevaluate member permissions
|
|
deleteTokenPermission := &requests.DeleteCommunityTokenPermission{
|
|
CommunityID: community.ID(),
|
|
PermissionID: tokenPermission.Id,
|
|
}
|
|
|
|
waitOnPermissionsReevaluated = waitOnCommunitiesEvent(s.owner, func(sub *communities.Subscription) bool {
|
|
if sub.Community == nil {
|
|
return false
|
|
}
|
|
return !checkRoleBasedOnThePermissionType(permissionType, &s.alice.identity.PublicKey, sub.Community)
|
|
})
|
|
|
|
response, err = s.owner.DeleteCommunityTokenPermission(deleteTokenPermission)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.Communities(), 1)
|
|
s.Require().False(response.Communities()[0].HasTokenPermissions())
|
|
|
|
community, err = s.owner.communitiesManager.GetByID(community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().False(community.HasTokenPermissions())
|
|
|
|
err = s.owner.communitiesManager.ForceMembersReevaluation(community.ID())
|
|
s.Require().NoError(err)
|
|
|
|
err = <-waitOnPermissionsReevaluated
|
|
s.Require().NoError(err)
|
|
|
|
community, err = s.owner.communitiesManager.GetByID(community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().True(community.HasMember(&s.alice.identity.PublicKey))
|
|
s.Require().False(checkRoleBasedOnThePermissionType(permissionType, &s.alice.identity.PublicKey, community))
|
|
}
|
|
|
|
func (s *MessengerCommunitiesTokenPermissionsSuite) TestReevaluateMemberAdminRoleInOpenCommunity_ERC20() {
|
|
s.testReevaluateMemberPrivilegedRoleInOpenCommunity(protobuf.CommunityTokenPermission_BECOME_ADMIN, protobuf.CommunityTokenType_ERC20)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesTokenPermissionsSuite) TestReevaluateMemberAdminRoleInOpenCommunity_ERC721() {
|
|
s.testReevaluateMemberPrivilegedRoleInOpenCommunity(protobuf.CommunityTokenPermission_BECOME_ADMIN, protobuf.CommunityTokenType_ERC721)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesTokenPermissionsSuite) TestReevaluateMemberTokenMasterRoleInOpenCommunity_ERC20() {
|
|
s.testReevaluateMemberPrivilegedRoleInOpenCommunity(protobuf.CommunityTokenPermission_BECOME_TOKEN_MASTER, protobuf.CommunityTokenType_ERC20)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesTokenPermissionsSuite) TestReevaluateMemberTokenMasterRoleInOpenCommunity_ERC721() {
|
|
s.testReevaluateMemberPrivilegedRoleInOpenCommunity(protobuf.CommunityTokenPermission_BECOME_TOKEN_MASTER, protobuf.CommunityTokenType_ERC721)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesTokenPermissionsSuite) testReevaluateMemberPrivilegedRoleInClosedCommunity(permissionType protobuf.CommunityTokenPermission_Type, tokenType protobuf.CommunityTokenType) {
|
|
community, _ := s.createCommunity()
|
|
|
|
amountInWei := "100000000000000000000"
|
|
decimals := uint64(18)
|
|
if tokenType == protobuf.CommunityTokenType_ERC721 {
|
|
amountInWei = "1"
|
|
decimals = 0
|
|
}
|
|
|
|
createTokenPermission := &requests.CreateCommunityTokenPermission{
|
|
CommunityID: community.ID(),
|
|
Type: permissionType,
|
|
TokenCriteria: []*protobuf.TokenCriteria{
|
|
{
|
|
Type: tokenType,
|
|
ContractAddresses: map[uint64]string{testChainID1: "0x123"},
|
|
Symbol: "TEST",
|
|
AmountInWei: amountInWei,
|
|
Decimals: decimals,
|
|
},
|
|
},
|
|
}
|
|
|
|
response, err := s.owner.CreateCommunityTokenPermission(createTokenPermission)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.Communities(), 1)
|
|
s.Require().True(response.Communities()[0].HasTokenPermissions())
|
|
|
|
createTokenMemberPermission := &requests.CreateCommunityTokenPermission{
|
|
CommunityID: community.ID(),
|
|
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
|
|
TokenCriteria: []*protobuf.TokenCriteria{
|
|
{
|
|
Type: tokenType,
|
|
ContractAddresses: map[uint64]string{testChainID1: "0x124"},
|
|
Symbol: "TEST2",
|
|
AmountInWei: amountInWei,
|
|
Decimals: decimals,
|
|
},
|
|
},
|
|
}
|
|
|
|
waitOnCommunityPermissionCreated := waitOnCommunitiesEvent(s.owner, func(sub *communities.Subscription) bool {
|
|
return len(sub.Community.TokenPermissions()) == 2
|
|
})
|
|
|
|
response, err = s.owner.CreateCommunityTokenPermission(createTokenMemberPermission)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.Communities(), 1)
|
|
|
|
community = response.Communities()[0]
|
|
s.Require().True(community.HasTokenPermissions())
|
|
s.Require().Len(community.TokenPermissions(), 2)
|
|
|
|
err = <-waitOnCommunityPermissionCreated
|
|
s.Require().NoError(err)
|
|
|
|
s.advertiseCommunityTo(community, s.alice)
|
|
|
|
var tokenPermission *communities.CommunityTokenPermission
|
|
var tokenMemberPermission *communities.CommunityTokenPermission
|
|
for _, permission := range community.TokenPermissions() {
|
|
if permission.Type == protobuf.CommunityTokenPermission_BECOME_MEMBER {
|
|
tokenMemberPermission = permission
|
|
} else {
|
|
tokenPermission = permission
|
|
}
|
|
}
|
|
|
|
s.makeAddressSatisfyTheCriteria(testChainID1, aliceAddress1, tokenPermission.TokenCriteria[0])
|
|
s.makeAddressSatisfyTheCriteria(testChainID1, aliceAddress1, tokenMemberPermission.TokenCriteria[0])
|
|
|
|
waitOnAliceAddedToCommunity := waitOnCommunitiesEvent(s.owner, func(sub *communities.Subscription) bool {
|
|
if sub.Community == nil {
|
|
return false
|
|
}
|
|
return checkRoleBasedOnThePermissionType(permissionType, &s.alice.identity.PublicKey, sub.Community)
|
|
})
|
|
|
|
// join community as a privileged user
|
|
s.joinCommunity(community, s.alice)
|
|
|
|
community, err = s.owner.communitiesManager.GetByID(community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().True(checkRoleBasedOnThePermissionType(permissionType, &s.alice.identity.PublicKey, community))
|
|
|
|
// the control node reevaluates the roles of the participants, checking that the privileged user has not lost his role
|
|
err = s.owner.communitiesManager.ForceMembersReevaluation(community.ID())
|
|
s.Require().NoError(err)
|
|
|
|
err = <-waitOnAliceAddedToCommunity
|
|
s.Require().NoError(err)
|
|
|
|
community, err = s.owner.communitiesManager.GetByID(community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().True(checkRoleBasedOnThePermissionType(permissionType, &s.alice.identity.PublicKey, community))
|
|
|
|
deleteTokenPermission := &requests.DeleteCommunityTokenPermission{
|
|
CommunityID: community.ID(),
|
|
PermissionID: tokenPermission.Id,
|
|
}
|
|
|
|
// remove privileged token permission and reevaluate member permissions
|
|
response, err = s.owner.DeleteCommunityTokenPermission(deleteTokenPermission)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.Communities(), 1)
|
|
s.Require().Len(response.Communities()[0].TokenPermissions(), 1)
|
|
|
|
community, err = s.owner.communitiesManager.GetByID(community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Communities()[0].TokenPermissions(), 1)
|
|
|
|
waitOnAliceLostPermission := waitOnCommunitiesEvent(s.owner, func(sub *communities.Subscription) bool {
|
|
if sub.Community == nil {
|
|
return false
|
|
}
|
|
return !checkRoleBasedOnThePermissionType(permissionType, &s.alice.identity.PublicKey, sub.Community)
|
|
})
|
|
|
|
err = s.owner.communitiesManager.ForceMembersReevaluation(community.ID())
|
|
s.Require().NoError(err)
|
|
|
|
err = <-waitOnAliceLostPermission
|
|
s.Require().NoError(err)
|
|
|
|
community, err = s.owner.communitiesManager.GetByID(community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().True(community.HasMember(&s.alice.identity.PublicKey))
|
|
s.Require().False(checkRoleBasedOnThePermissionType(permissionType, &s.alice.identity.PublicKey, community))
|
|
|
|
// delete member permissions and reevaluate user permissions
|
|
deleteMemberTokenPermission := &requests.DeleteCommunityTokenPermission{
|
|
CommunityID: community.ID(),
|
|
PermissionID: tokenMemberPermission.Id,
|
|
}
|
|
|
|
waitOnReevaluation := waitOnCommunitiesEvent(s.owner, func(sub *communities.Subscription) bool {
|
|
if sub.Community == nil {
|
|
return false
|
|
}
|
|
return !checkRoleBasedOnThePermissionType(permissionType, &s.alice.identity.PublicKey, sub.Community)
|
|
})
|
|
|
|
response, err = s.owner.DeleteCommunityTokenPermission(deleteMemberTokenPermission)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.Communities(), 1)
|
|
s.Require().Len(response.Communities()[0].TokenPermissions(), 0)
|
|
|
|
community, err = s.owner.communitiesManager.GetByID(community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Communities()[0].TokenPermissions(), 0)
|
|
|
|
err = s.owner.communitiesManager.ForceMembersReevaluation(community.ID())
|
|
s.Require().NoError(err)
|
|
|
|
err = <-waitOnReevaluation
|
|
s.Require().NoError(err)
|
|
|
|
community, err = s.owner.communitiesManager.GetByID(community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().True(community.HasMember(&s.alice.identity.PublicKey))
|
|
|
|
s.Require().False(checkRoleBasedOnThePermissionType(permissionType, &s.alice.identity.PublicKey, community))
|
|
}
|
|
|
|
func (s *MessengerCommunitiesTokenPermissionsSuite) TestReevaluateMemberAdminRoleInClosedCommunity_ERC20() {
|
|
s.testReevaluateMemberPrivilegedRoleInClosedCommunity(protobuf.CommunityTokenPermission_BECOME_ADMIN, protobuf.CommunityTokenType_ERC20)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesTokenPermissionsSuite) TestReevaluateMemberAdminRoleInClosedCommunity_ERC721() {
|
|
s.testReevaluateMemberPrivilegedRoleInClosedCommunity(protobuf.CommunityTokenPermission_BECOME_ADMIN, protobuf.CommunityTokenType_ERC721)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesTokenPermissionsSuite) TestReevaluateMemberTokenMasterRoleInClosedCommunity_ERC20() {
|
|
s.testReevaluateMemberPrivilegedRoleInClosedCommunity(protobuf.CommunityTokenPermission_BECOME_TOKEN_MASTER, protobuf.CommunityTokenType_ERC20)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesTokenPermissionsSuite) TestReevaluateMemberTokenMasterRoleInClosedCommunity_ERC721() {
|
|
s.testReevaluateMemberPrivilegedRoleInClosedCommunity(protobuf.CommunityTokenPermission_BECOME_TOKEN_MASTER, protobuf.CommunityTokenType_ERC721)
|
|
}
|
|
|
|
func checkRoleBasedOnThePermissionType(permissionType protobuf.CommunityTokenPermission_Type, member *ecdsa.PublicKey, community *communities.Community) bool {
|
|
switch permissionType {
|
|
case protobuf.CommunityTokenPermission_BECOME_ADMIN:
|
|
return community.IsMemberAdmin(member)
|
|
case protobuf.CommunityTokenPermission_BECOME_TOKEN_MASTER:
|
|
return community.IsMemberTokenMaster(member)
|
|
default:
|
|
panic("Unknown permission, please, update the test")
|
|
}
|
|
}
|
|
|
|
func (s *MessengerCommunitiesTokenPermissionsSuite) TestResendEncryptionKeyOnBackupRestore() {
|
|
community, chat := s.createCommunity()
|
|
|
|
// bob joins the community
|
|
s.advertiseCommunityTo(community, s.bob)
|
|
s.joinCommunity(community, s.bob)
|
|
|
|
// setup view channel permission
|
|
channelPermissionRequest := requests.CreateCommunityTokenPermission{
|
|
CommunityID: community.ID(),
|
|
Type: protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL,
|
|
TokenCriteria: []*protobuf.TokenCriteria{
|
|
&protobuf.TokenCriteria{
|
|
Type: protobuf.CommunityTokenType_ERC20,
|
|
ContractAddresses: map[uint64]string{testChainID1: "0x123"},
|
|
Symbol: "TEST",
|
|
AmountInWei: "100000000000000000000",
|
|
Decimals: uint64(18),
|
|
},
|
|
},
|
|
ChatIds: []string{chat.ID},
|
|
}
|
|
|
|
// make bob satisfy channel criteria
|
|
s.makeAddressSatisfyTheCriteria(testChainID1, bobAddress, channelPermissionRequest.TokenCriteria[0])
|
|
defer s.resetMockedBalances() // reset mocked balances, this test in run with different test cases
|
|
|
|
response, err := s.owner.CreateCommunityTokenPermission(&channelPermissionRequest)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Communities(), 1)
|
|
s.Require().True(s.owner.communitiesManager.IsChannelEncrypted(community.IDString(), chat.ID))
|
|
|
|
// reevalate community member permissions in order get encryption keys
|
|
community, err = s.owner.communitiesManager.GetByID(community.ID())
|
|
s.Require().NoError(err)
|
|
|
|
waitOnChannelKeyToBeDistributedToBob := s.waitOnKeyDistribution(func(sub *CommunityAndKeyActions) bool {
|
|
action, ok := sub.keyActions.ChannelKeysActions[chat.CommunityChatID()]
|
|
if !ok || action.ActionType != communities.EncryptionKeyAdd {
|
|
return false
|
|
}
|
|
|
|
_, ok = action.Members[common.PubkeyToHex(&s.bob.identity.PublicKey)]
|
|
return ok
|
|
})
|
|
|
|
err = <-waitOnChannelKeyToBeDistributedToBob
|
|
s.Require().NoError(err)
|
|
|
|
// bob receives community changes
|
|
// channel members should not be empty,
|
|
// this info is available only to channel members with encryption key
|
|
_, err = WaitOnMessengerResponse(
|
|
s.bob,
|
|
func(r *MessengerResponse) bool {
|
|
c, err := s.bob.GetCommunityByID(community.ID())
|
|
if err != nil {
|
|
return false
|
|
}
|
|
if c == nil {
|
|
return false
|
|
}
|
|
channel := c.Chats()[chat.CommunityChatID()]
|
|
if channel != nil && len(channel.Members) < 2 {
|
|
return false
|
|
}
|
|
|
|
return channel.Permissions != nil
|
|
},
|
|
"no community that satisfies criteria",
|
|
)
|
|
s.Require().NoError(err)
|
|
|
|
// owner send message to the channel
|
|
msg := s.sendChatMessage(s.owner, chat.ID, "hello to encrypted channel")
|
|
|
|
// bob can read the message
|
|
response, err = WaitOnMessengerResponse(
|
|
s.bob,
|
|
func(r *MessengerResponse) bool {
|
|
_, ok := r.messages[msg.ID]
|
|
return ok
|
|
},
|
|
"no messages",
|
|
)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Messages(), 1)
|
|
s.Require().Equal(msg.Text, response.Messages()[0].Text)
|
|
|
|
// Simulate backup creation and handling backup message
|
|
// As a result, bob sends request to resend encryption keys to the owner
|
|
clock, _ := s.bob.getLastClockWithRelatedChat()
|
|
|
|
community, err = s.owner.communitiesManager.GetByID(community.ID())
|
|
s.Require().NoError(err)
|
|
|
|
backupMessage, err := s.bob.backupCommunity(community, clock)
|
|
s.Require().NoError(err)
|
|
|
|
err = s.bob.HandleBackup(s.bob.buildMessageState(), backupMessage, nil)
|
|
s.Require().NoError(err)
|
|
|
|
// regenerate key for the channel in order to check that owner will send keys
|
|
// on bob request from `HandleBackup`
|
|
_, err = s.owner.encryptor.GenerateHashRatchetKey([]byte(community.IDString() + chat.CommunityChatID()))
|
|
s.Require().NoError(err)
|
|
|
|
testCommunitiesKeyDistributor, ok := s.owner.communitiesKeyDistributor.(*TestCommunitiesKeyDistributor)
|
|
s.Require().True(ok)
|
|
s.Require().NotNil(testCommunitiesKeyDistributor)
|
|
subscription := testCommunitiesKeyDistributor.subscribeToKeyDistribution()
|
|
|
|
// `HandleCommunityEncryptionKeysRequest` does not return any response
|
|
// To make sure that `HandleCommunityEncryptionKeysRequest` was called and new keys sent
|
|
// we will subscribe to key distribution
|
|
checkKeyWasSent := func() bool {
|
|
var sub *CommunityAndKeyActions
|
|
select {
|
|
case sub = <-subscription:
|
|
default:
|
|
return false // No data available, return false
|
|
}
|
|
action, ok := sub.keyActions.ChannelKeysActions[chat.CommunityChatID()]
|
|
if !ok || action.ActionType != communities.EncryptionKeySendToMembers {
|
|
return false
|
|
}
|
|
|
|
_, ok = action.Members[common.PubkeyToHex(&s.bob.identity.PublicKey)]
|
|
return ok
|
|
}
|
|
|
|
_, err = WaitOnMessengerResponse(
|
|
s.owner,
|
|
func(r *MessengerResponse) bool {
|
|
return checkKeyWasSent()
|
|
},
|
|
"no community that satisfies criteria",
|
|
)
|
|
|
|
testCommunitiesKeyDistributor.unsubscribeFromKeyDistribution(subscription)
|
|
|
|
s.Require().NoError(err)
|
|
|
|
// msg will be encrypted using new keys
|
|
msg = s.sendChatMessage(s.owner, chat.ID, "hello to closed channel with the new key")
|
|
|
|
// bob received new keys and can read the message
|
|
response, err = WaitOnMessengerResponse(
|
|
s.bob,
|
|
func(r *MessengerResponse) bool {
|
|
_, ok := r.messages[msg.ID]
|
|
return ok
|
|
},
|
|
"no messages",
|
|
)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Messages(), 1)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesTokenPermissionsSuite) TestReevaluateMemberPermissionsPerformance() {
|
|
// This test is created for a performance degradation tracking for reevaluateMember permissions
|
|
// current scenario mostly track channels permissions reevaluating, but feel free to expand it to
|
|
// other scenarios or test you performance improvements
|
|
|
|
// in average, it took nearly 100-105 ms to check one permission for a current scenario:
|
|
// - 10 members
|
|
// - 10 channels
|
|
// - one permission (channel permission for all 10 channels is set up)
|
|
|
|
// currently, adding any new permission to test must twice the current test average time
|
|
|
|
community, chat := s.createCommunity()
|
|
|
|
community, err := s.owner.communitiesManager.GetByID(community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().Len(community.Chats(), 1)
|
|
|
|
requestToJoin := &communities.RequestToJoin{
|
|
Clock: uint64(time.Now().Unix()),
|
|
CommunityID: community.ID(),
|
|
State: communities.RequestToJoinStateAccepted,
|
|
RevealedAccounts: []*protobuf.RevealedAccount{
|
|
{
|
|
Address: bobAddress,
|
|
ChainIds: []uint64{testChainID1},
|
|
IsAirdropAddress: true,
|
|
Signature: []byte("test"),
|
|
},
|
|
},
|
|
}
|
|
communityRole := []protobuf.CommunityMember_Roles{}
|
|
|
|
keysCount := 10
|
|
|
|
for i := 0; i < keysCount; i++ {
|
|
privateKey, err := crypto.GenerateKey()
|
|
s.Require().NoError(err)
|
|
|
|
memberPubKeyStr := common.PubkeyToHex(&privateKey.PublicKey)
|
|
requestId := communities.CalculateRequestID(memberPubKeyStr, community.ID())
|
|
requestToJoin.ID = requestId
|
|
requestToJoin.PublicKey = memberPubKeyStr
|
|
|
|
err = s.owner.communitiesManager.SaveRequestToJoin(requestToJoin)
|
|
s.Require().NoError(err)
|
|
err = s.owner.communitiesManager.SaveRequestToJoinRevealedAddresses(requestId, requestToJoin.RevealedAccounts)
|
|
s.Require().NoError(err)
|
|
_, err = community.AddMember(&privateKey.PublicKey, communityRole, requestToJoin.Clock)
|
|
s.Require().NoError(err)
|
|
_, err = community.AddMemberToChat(chat.CommunityChatID(), &privateKey.PublicKey, communityRole, protobuf.CommunityMember_CHANNEL_ROLE_POSTER)
|
|
s.Require().NoError(err)
|
|
}
|
|
|
|
s.Require().Equal(community.MembersCount(), keysCount+1) // 1 is owner
|
|
|
|
chatsCount := 9 // in total will be 10, 1 channel were created during creating the community
|
|
|
|
for i := 0; i < chatsCount; i++ {
|
|
newChat := &protobuf.CommunityChat{
|
|
Permissions: &protobuf.CommunityPermissions{
|
|
Access: protobuf.CommunityPermissions_AUTO_ACCEPT,
|
|
},
|
|
Identity: &protobuf.ChatIdentity{
|
|
DisplayName: "name-" + strconv.Itoa(i),
|
|
Description: "",
|
|
},
|
|
}
|
|
|
|
chatID := uuid.New().String()
|
|
_, err = community.CreateChat(chatID, newChat)
|
|
s.Require().NoError(err)
|
|
}
|
|
|
|
s.Require().Len(community.Chats(), chatsCount+1) // 1 chat were created during community creation
|
|
|
|
err = s.owner.communitiesManager.SaveCommunity(community)
|
|
s.Require().NoError(err)
|
|
|
|
// setup view channel permission
|
|
channelPermissionRequest := requests.CreateCommunityTokenPermission{
|
|
CommunityID: community.ID(),
|
|
Type: protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL,
|
|
TokenCriteria: []*protobuf.TokenCriteria{
|
|
&protobuf.TokenCriteria{
|
|
Type: protobuf.CommunityTokenType_ERC20,
|
|
ContractAddresses: map[uint64]string{testChainID1: "0x123"},
|
|
Symbol: "TEST",
|
|
AmountInWei: "100000000000000000000",
|
|
Decimals: uint64(18),
|
|
},
|
|
},
|
|
ChatIds: community.ChatIDs(),
|
|
}
|
|
|
|
s.makeAddressSatisfyTheCriteria(testChainID1, bobAddress, channelPermissionRequest.TokenCriteria[0])
|
|
defer s.resetMockedBalances() // reset mocked balances, this test in run with different test cases
|
|
|
|
// create permission using communitiesManager in order not to launch blocking reevaluation loop
|
|
community, _, err = s.owner.communitiesManager.CreateCommunityTokenPermission(&channelPermissionRequest)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(community.TokenPermissions(), 1)
|
|
|
|
for _, ids := range community.ChatIDs() {
|
|
s.Require().True(s.owner.communitiesManager.IsChannelEncrypted(community.IDString(), ids))
|
|
}
|
|
|
|
// force owner to reevaluate channel members
|
|
// in production it will happen automatically, by periodic check
|
|
start := time.Now()
|
|
_, _, err = s.owner.communitiesManager.ReevaluateMembers(community.ID())
|
|
s.Require().NoError(err)
|
|
|
|
elapsed := time.Since(start)
|
|
|
|
fmt.Println("ReevaluateMembers Time: ", elapsed)
|
|
s.Require().Less(elapsed.Seconds(), 2.0)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesTokenPermissionsSuite) TestImportDecryptedArchiveMessages() {
|
|
// 1.1. Create community
|
|
community, chat := s.createCommunity()
|
|
|
|
// 1.2. Setup permissions
|
|
communityPermission := &requests.CreateCommunityTokenPermission{
|
|
CommunityID: community.ID(),
|
|
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
|
|
TokenCriteria: []*protobuf.TokenCriteria{
|
|
{
|
|
Type: protobuf.CommunityTokenType_ERC20,
|
|
ContractAddresses: map[uint64]string{testChainID1: "0x124"},
|
|
Symbol: "TEST2",
|
|
AmountInWei: "100000000000000000000",
|
|
Decimals: uint64(18),
|
|
},
|
|
},
|
|
}
|
|
|
|
channelPermission := &requests.CreateCommunityTokenPermission{
|
|
CommunityID: community.ID(),
|
|
Type: protobuf.CommunityTokenPermission_CAN_VIEW_AND_POST_CHANNEL,
|
|
ChatIds: []string{chat.ID},
|
|
TokenCriteria: []*protobuf.TokenCriteria{
|
|
{
|
|
Type: protobuf.CommunityTokenType_ERC20,
|
|
ContractAddresses: map[uint64]string{testChainID1: "0x124"},
|
|
Symbol: "TEST2",
|
|
AmountInWei: "200000000000000000000",
|
|
Decimals: uint64(18),
|
|
},
|
|
},
|
|
}
|
|
|
|
waitOnChannelKeyAdded := s.waitOnKeyDistribution(func(sub *CommunityAndKeyActions) bool {
|
|
action, ok := sub.keyActions.ChannelKeysActions[chat.CommunityChatID()]
|
|
if !ok || action.ActionType != communities.EncryptionKeyAdd {
|
|
return false
|
|
}
|
|
_, ok = action.Members[common.PubkeyToHex(&s.owner.identity.PublicKey)]
|
|
return ok
|
|
})
|
|
|
|
waitOnCommunityPermissionCreated := waitOnCommunitiesEvent(s.owner, func(sub *communities.Subscription) bool {
|
|
return len(sub.Community.TokenPermissions()) == 2
|
|
})
|
|
|
|
response, err := s.owner.CreateCommunityTokenPermission(communityPermission)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.Communities(), 1)
|
|
|
|
response, err = s.owner.CreateCommunityTokenPermission(channelPermission)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.Communities(), 1)
|
|
|
|
community = response.Communities()[0]
|
|
s.Require().True(community.HasTokenPermissions())
|
|
s.Require().Len(community.TokenPermissions(), 2)
|
|
|
|
err = <-waitOnCommunityPermissionCreated
|
|
s.Require().NoError(err)
|
|
s.Require().True(community.Encrypted())
|
|
|
|
err = <-waitOnChannelKeyAdded
|
|
s.Require().NoError(err)
|
|
|
|
// 2. Owner: Send a message A
|
|
messageText1 := RandomLettersString(10)
|
|
message1 := s.sendChatMessage(s.owner, chat.ID, messageText1)
|
|
|
|
// 2.2. Retrieve own message (to make it stored in the archive later)
|
|
_, err = s.owner.RetrieveAll()
|
|
s.Require().NoError(err)
|
|
|
|
// 3. Owner: Create community archive
|
|
const partition = 2 * time.Minute
|
|
messageDate := time.UnixMilli(int64(message1.Timestamp))
|
|
startDate := messageDate.Add(-time.Minute)
|
|
endDate := messageDate.Add(time.Minute)
|
|
topic := types.BytesToTopic(transport.ToTopic(chat.ID))
|
|
topics := []types.TopicType{topic}
|
|
|
|
torrentConfig := params.TorrentConfig{
|
|
Enabled: true,
|
|
DataDir: os.TempDir() + "/archivedata",
|
|
TorrentDir: os.TempDir() + "/torrents",
|
|
Port: 0,
|
|
}
|
|
|
|
// Share archive directory between all users
|
|
s.owner.archiveManager.SetTorrentConfig(&torrentConfig)
|
|
s.bob.archiveManager.SetTorrentConfig(&torrentConfig)
|
|
s.owner.config.messengerSignalsHandler = &MessengerSignalsHandlerMock{}
|
|
s.bob.config.messengerSignalsHandler = &MessengerSignalsHandlerMock{}
|
|
|
|
archiveIDs, err := s.owner.archiveManager.CreateHistoryArchiveTorrentFromDB(community.ID(), topics, startDate, endDate, partition, community.Encrypted())
|
|
s.Require().NoError(err)
|
|
s.Require().Len(archiveIDs, 1)
|
|
|
|
community, err = s.owner.GetCommunityByID(community.ID())
|
|
s.Require().NoError(err)
|
|
|
|
// 4. Bob: join community (satisfying membership, but not channel permissions)
|
|
s.makeAddressSatisfyTheCriteria(testChainID1, bobAddress, communityPermission.TokenCriteria[0])
|
|
s.advertiseCommunityTo(community, s.bob)
|
|
|
|
waitForKeysDistributedToBob := s.waitOnKeyDistribution(func(sub *CommunityAndKeyActions) bool {
|
|
action := sub.keyActions.CommunityKeyAction
|
|
if action.ActionType != communities.EncryptionKeySendToMembers {
|
|
return false
|
|
}
|
|
_, ok := action.Members[s.bob.IdentityPublicKeyString()]
|
|
return ok
|
|
})
|
|
|
|
s.joinCommunity(community, s.bob)
|
|
|
|
err = <-waitForKeysDistributedToBob
|
|
s.Require().NoError(err)
|
|
|
|
// 5. Bob: Import community archive
|
|
// The archive is successfully decrypted, but the message inside is not.
|
|
// https://github.com/status-im/status-desktop/issues/13105 can be reproduced at this stage
|
|
// by forcing `encryption.ErrHashRatchetGroupIDNotFound` in `ExtractMessagesFromHistoryArchive` after decryption here:
|
|
// https://github.com/status-im/status-go/blob/6c82a6c2be7ebed93bcae3b9cf5053da3820de50/protocol/communities/manager.go#L4403
|
|
|
|
// Ensure owner has archive
|
|
archiveIndex, err := s.owner.archiveManager.LoadHistoryArchiveIndexFromFile(s.owner.identity, community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().Len(archiveIndex.Archives, 1)
|
|
|
|
// Ensure bob has archive (because they share same local directory)
|
|
archiveIndex, err = s.bob.archiveManager.LoadHistoryArchiveIndexFromFile(s.bob.identity, community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().Len(archiveIndex.Archives, 1)
|
|
|
|
archiveHash := maps.Keys(archiveIndex.Archives)[0]
|
|
|
|
// Save message archive ID as in
|
|
// https://github.com/status-im/status-go/blob/6c82a6c2be7ebed93bcae3b9cf5053da3820de50/protocol/communities/manager.go#L4325-L4336
|
|
err = s.bob.archiveManager.SaveMessageArchiveID(community.ID(), archiveHash)
|
|
s.Require().NoError(err)
|
|
|
|
// Import archive
|
|
s.bob.importDelayer.once.Do(func() {
|
|
close(s.bob.importDelayer.wait)
|
|
})
|
|
cancel := make(chan struct{})
|
|
err = s.bob.importHistoryArchives(community.ID(), cancel)
|
|
s.Require().NoError(err)
|
|
|
|
// Ensure message1 wasn't imported, as it's encrypted, and we don't have access to the channel
|
|
receivedMessage1, err := s.bob.MessageByID(message1.ID)
|
|
s.Require().Nil(receivedMessage1)
|
|
s.Require().Error(err)
|
|
|
|
chatID := []byte(chat.ID)
|
|
hashRatchetMessagesCount, err := s.bob.persistence.GetHashRatchetMessagesCountForGroup(chatID)
|
|
s.Require().NoError(err)
|
|
s.Require().Equal(1, hashRatchetMessagesCount)
|
|
|
|
// Make bob satisfy channel criteria
|
|
waitOnChannelKeyToBeDistributedToBob := s.waitOnKeyDistribution(func(sub *CommunityAndKeyActions) bool {
|
|
action, ok := sub.keyActions.ChannelKeysActions[chat.CommunityChatID()]
|
|
if !ok || action.ActionType != communities.EncryptionKeySendToMembers {
|
|
return false
|
|
}
|
|
_, ok = action.Members[common.PubkeyToHex(&s.bob.identity.PublicKey)]
|
|
return ok
|
|
})
|
|
|
|
s.makeAddressSatisfyTheCriteria(testChainID1, bobAddress, channelPermission.TokenCriteria[0])
|
|
|
|
// force owner to reevaluate channel members
|
|
// in production it will happen automatically, by periodic check
|
|
err = s.owner.communitiesManager.ForceMembersReevaluation(community.ID())
|
|
s.Require().NoError(err)
|
|
|
|
err = <-waitOnChannelKeyToBeDistributedToBob
|
|
s.Require().NoError(err)
|
|
|
|
// Finally ensure that the message from archive was retrieved and decrypted
|
|
|
|
// NOTE: In theory a single RetrieveAll call should be enough,
|
|
// because we immediately process all hash ratchet messages
|
|
response, err = s.bob.RetrieveAll()
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Messages(), 1)
|
|
|
|
receivedMessage1, ok := response.messages[message1.ID]
|
|
s.Require().True(ok)
|
|
s.Require().Equal(messageText1, receivedMessage1.Text)
|
|
}
|
|
|
|
func (s *MessengerCommunitiesTokenPermissionsSuite) TestDeleteChannelWithTokenPermission() {
|
|
// Setup community with two permitted channels
|
|
community, firstChat := s.createCommunity()
|
|
|
|
response, err := s.owner.CreateCommunityChat(community.ID(), &protobuf.CommunityChat{
|
|
Permissions: &protobuf.CommunityPermissions{
|
|
Access: protobuf.CommunityPermissions_AUTO_ACCEPT,
|
|
},
|
|
Identity: &protobuf.ChatIdentity{
|
|
DisplayName: "new channel",
|
|
Emoji: "",
|
|
Description: "chat created after joining the community",
|
|
},
|
|
})
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Chats(), 1)
|
|
secondChat := response.Chats()[0]
|
|
|
|
channelPermission := &requests.CreateCommunityTokenPermission{
|
|
CommunityID: community.ID(),
|
|
Type: protobuf.CommunityTokenPermission_CAN_VIEW_AND_POST_CHANNEL,
|
|
ChatIds: []string{firstChat.ID, secondChat.ID},
|
|
TokenCriteria: []*protobuf.TokenCriteria{
|
|
{
|
|
Type: protobuf.CommunityTokenType_ERC20,
|
|
ContractAddresses: map[uint64]string{testChainID1: "0x124"},
|
|
Symbol: "TEST2",
|
|
AmountInWei: "200000000000000000000",
|
|
Decimals: uint64(18),
|
|
},
|
|
},
|
|
}
|
|
|
|
response, err = s.owner.CreateCommunityTokenPermission(channelPermission)
|
|
s.Require().NoError(err)
|
|
s.Require().NotNil(response)
|
|
s.Require().Len(response.Communities(), 1)
|
|
|
|
// Make sure both channels are covered with permission
|
|
community, err = s.owner.GetCommunityByID(community.ID())
|
|
s.Require().NoError(err)
|
|
s.Require().Len(community.Chats(), 2)
|
|
s.Require().Len(community.TokenPermissions(), 1)
|
|
for _, permission := range community.TokenPermissions() {
|
|
s.Require().Len(permission.ChatIds, 2)
|
|
s.Require().True(permission.HasChat(firstChat.ID))
|
|
s.Require().True(permission.HasChat(secondChat.ID))
|
|
}
|
|
|
|
// Delete first community channel
|
|
response, err = s.owner.DeleteCommunityChat(community.ID(), firstChat.ID)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Communities(), 1)
|
|
community = response.Communities()[0]
|
|
s.Require().Len(community.Chats(), 1)
|
|
for _, permission := range community.TokenPermissions() {
|
|
s.Require().Len(permission.ChatIds, 1)
|
|
s.Require().False(permission.HasChat(firstChat.ID))
|
|
s.Require().True(permission.HasChat(secondChat.ID))
|
|
}
|
|
|
|
// Delete second community channel
|
|
response, err = s.owner.DeleteCommunityChat(community.ID(), secondChat.ID)
|
|
s.Require().NoError(err)
|
|
s.Require().Len(response.Communities(), 1)
|
|
community = response.Communities()[0]
|
|
s.Require().Len(community.Chats(), 0)
|
|
s.Require().Len(community.TokenPermissions(), 0)
|
|
}
|