feat: add CommunityEncryptionKeyActions and its evaluation logic

Added utility that evaluates necessary encryption key actions based on
community changes.

part of: status-im/status-desktop#10998
This commit is contained in:
Patryk Osmaczko 2023-07-13 19:49:19 +02:00 committed by osmaczko
parent 84bfdf4aab
commit d9df8b6150
5 changed files with 1131 additions and 200 deletions

View File

@ -1018,6 +1018,14 @@ func (o *Community) UpdateCommunityDescription(description *protobuf.CommunityDe
o.mutex.Lock() o.mutex.Lock()
defer o.mutex.Unlock() defer o.mutex.Unlock()
// This is done in case tags are updated and a client sends unknown tags
description.Tags = requests.RemoveUnknownAndDeduplicateTags(description.Tags)
err := ValidateCommunityDescription(description)
if err != nil {
return nil, err
}
response := o.emptyCommunityChanges() response := o.emptyCommunityChanges()
// allowEqualClock == true only if this was a description from the handling request to join sent by an admin // allowEqualClock == true only if this was a description from the handling request to join sent by an admin
@ -1029,9 +1037,10 @@ func (o *Community) UpdateCommunityDescription(description *protobuf.CommunityDe
return response, nil return response, nil
} }
response, err := o.collectCommunityChanges(description) // We only calculate changes if we joined/spectated the community or we requested access, otherwise not interested
if err != nil { if o.config.Joined || o.config.Spectated || o.config.RequestedToJoinAt > 0 {
return nil, err response = EvaluateCommunityChanges(o.config.CommunityDescription, description)
response.Community = o
} }
o.config.CommunityDescription = description o.config.CommunityDescription = description
@ -1384,14 +1393,18 @@ func (o *Community) HasTokenPermissions() bool {
return len(o.config.CommunityDescription.TokenPermissions) > 0 return len(o.config.CommunityDescription.TokenPermissions) > 0
} }
func (o *Community) TokenPermissionsByType(permissionType protobuf.CommunityTokenPermission_Type) []*protobuf.CommunityTokenPermission { func TokenPermissionsByType(permissions map[string]*protobuf.CommunityTokenPermission, permissionType protobuf.CommunityTokenPermission_Type) []*protobuf.CommunityTokenPermission {
permissions := make([]*protobuf.CommunityTokenPermission, 0) result := make([]*protobuf.CommunityTokenPermission, 0)
for _, tokenPermission := range o.TokenPermissions() { for _, tokenPermission := range permissions {
if tokenPermission.Type == permissionType { if tokenPermission.Type == permissionType {
permissions = append(permissions, tokenPermission) result = append(result, tokenPermission)
} }
} }
return permissions return result
}
func (o *Community) TokenPermissionsByType(permissionType protobuf.CommunityTokenPermission_Type) []*protobuf.CommunityTokenPermission {
return TokenPermissionsByType(o.TokenPermissions(), permissionType)
} }
func (o *Community) ChannelTokenPermissionsByType(channelID string, permissionType protobuf.CommunityTokenPermission_Type) []*protobuf.CommunityTokenPermission { func (o *Community) ChannelTokenPermissionsByType(channelID string, permissionType protobuf.CommunityTokenPermission_Type) []*protobuf.CommunityTokenPermission {
@ -1809,6 +1822,38 @@ 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) {
o.mutex.Lock()
defer o.mutex.Unlock()
if !o.IsOwnerOrAdmin() {
return nil, ErrNotAuthorized
}
memberKey := common.PubkeyToHex(publicKey)
changes := o.emptyCommunityChanges()
chat, ok := o.config.CommunityDescription.Chats[chatID]
if !ok {
return nil, ErrChatNotFound
}
if chat.Members == nil {
chat.Members = make(map[string]*protobuf.CommunityMember)
}
chat.Members[memberKey] = &protobuf.CommunityMember{
Roles: roles,
}
changes.ChatsModified[chatID] = &CommunityChatChanges{
ChatModified: chat,
MembersAdded: map[string]*protobuf.CommunityMember{
memberKey: chat.Members[memberKey],
},
}
return changes, nil
}
func (o *Community) ChatIDs() (chatIDs []string) { func (o *Community) ChatIDs() (chatIDs []string) {
for id := range o.config.CommunityDescription.Chats { for id := range o.config.CommunityDescription.Chats {
chatIDs = append(chatIDs, o.IDString()+id) chatIDs = append(chatIDs, o.IDString()+id)
@ -2044,189 +2089,6 @@ func (o *Community) addCommunityMember(pk *ecdsa.PublicKey, member *protobuf.Com
o.config.CommunityDescription.Members[memberKey] = member o.config.CommunityDescription.Members[memberKey] = member
} }
func (o *Community) collectCommunityChanges(description *protobuf.CommunityDescription) (*CommunityChanges, error) {
// This is done in case tags are updated and a client sends unknown tags
description.Tags = requests.RemoveUnknownAndDeduplicateTags(description.Tags)
err := ValidateCommunityDescription(description)
if err != nil {
return nil, err
}
response := o.emptyCommunityChanges()
// We only calculate changes if we joined/spectated the community or we requested access, otherwise not interested
if o.config.Joined || o.config.Spectated || o.config.RequestedToJoinAt > 0 {
// Check for new members at the org level
for pk, member := range description.Members {
if _, ok := o.config.CommunityDescription.Members[pk]; !ok {
if response.MembersAdded == nil {
response.MembersAdded = make(map[string]*protobuf.CommunityMember)
}
response.MembersAdded[pk] = member
}
}
// Check for removed members at the org level
for pk, member := range o.config.CommunityDescription.Members {
if _, ok := description.Members[pk]; !ok {
if response.MembersRemoved == nil {
response.MembersRemoved = make(map[string]*protobuf.CommunityMember)
}
response.MembersRemoved[pk] = member
}
}
// check for removed chats
for chatID, chat := range o.config.CommunityDescription.Chats {
if description.Chats == nil {
description.Chats = make(map[string]*protobuf.CommunityChat)
}
if _, ok := description.Chats[chatID]; !ok {
if response.ChatsRemoved == nil {
response.ChatsRemoved = make(map[string]*protobuf.CommunityChat)
}
response.ChatsRemoved[chatID] = chat
}
}
for chatID, chat := range description.Chats {
if o.config.CommunityDescription.Chats == nil {
o.config.CommunityDescription.Chats = make(map[string]*protobuf.CommunityChat)
}
if _, ok := o.config.CommunityDescription.Chats[chatID]; !ok {
if response.ChatsAdded == nil {
response.ChatsAdded = make(map[string]*protobuf.CommunityChat)
}
response.ChatsAdded[chatID] = chat
} else {
// Check for members added
for pk, member := range description.Chats[chatID].Members {
if _, ok := o.config.CommunityDescription.Chats[chatID].Members[pk]; !ok {
if response.ChatsModified[chatID] == nil {
response.ChatsModified[chatID] = &CommunityChatChanges{
MembersAdded: make(map[string]*protobuf.CommunityMember),
MembersRemoved: make(map[string]*protobuf.CommunityMember),
}
}
response.ChatsModified[chatID].MembersAdded[pk] = member
}
}
// check for members removed
for pk, member := range o.config.CommunityDescription.Chats[chatID].Members {
if _, ok := description.Chats[chatID].Members[pk]; !ok {
if response.ChatsModified[chatID] == nil {
response.ChatsModified[chatID] = &CommunityChatChanges{
MembersAdded: make(map[string]*protobuf.CommunityMember),
MembersRemoved: make(map[string]*protobuf.CommunityMember),
}
}
response.ChatsModified[chatID].MembersRemoved[pk] = member
}
}
// check if first message timestamp was modified
if o.config.CommunityDescription.Chats[chatID].Identity.FirstMessageTimestamp !=
description.Chats[chatID].Identity.FirstMessageTimestamp {
if response.ChatsModified[chatID] == nil {
response.ChatsModified[chatID] = &CommunityChatChanges{
MembersAdded: make(map[string]*protobuf.CommunityMember),
MembersRemoved: make(map[string]*protobuf.CommunityMember),
}
}
response.ChatsModified[chatID].FirstMessageTimestampModified = description.Chats[chatID].Identity.FirstMessageTimestamp
}
}
}
// Check for categories that were removed
for categoryID := range o.config.CommunityDescription.Categories {
if description.Categories == nil {
description.Categories = make(map[string]*protobuf.CommunityCategory)
}
if description.Chats == nil {
description.Chats = make(map[string]*protobuf.CommunityChat)
}
if _, ok := description.Categories[categoryID]; !ok {
response.CategoriesRemoved = append(response.CategoriesRemoved, categoryID)
}
if o.config.CommunityDescription.Chats == nil {
o.config.CommunityDescription.Chats = make(map[string]*protobuf.CommunityChat)
}
}
// Check for categories that were added
for categoryID, category := range description.Categories {
if o.config.CommunityDescription.Categories == nil {
o.config.CommunityDescription.Categories = make(map[string]*protobuf.CommunityCategory)
}
if _, ok := o.config.CommunityDescription.Categories[categoryID]; !ok {
if response.CategoriesAdded == nil {
response.CategoriesAdded = make(map[string]*protobuf.CommunityCategory)
}
response.CategoriesAdded[categoryID] = category
} else {
if o.config.CommunityDescription.Categories[categoryID].Name != category.Name || o.config.CommunityDescription.Categories[categoryID].Position != category.Position {
response.CategoriesModified[categoryID] = category
}
}
}
// Check for chat categories that were modified
for chatID, chat := range description.Chats {
if o.config.CommunityDescription.Chats == nil {
o.config.CommunityDescription.Chats = make(map[string]*protobuf.CommunityChat)
}
if _, ok := o.config.CommunityDescription.Chats[chatID]; !ok {
continue // It's a new chat
}
if o.config.CommunityDescription.Chats[chatID].CategoryId != chat.CategoryId {
if response.ChatsModified[chatID] == nil {
response.ChatsModified[chatID] = &CommunityChatChanges{
MembersAdded: make(map[string]*protobuf.CommunityMember),
MembersRemoved: make(map[string]*protobuf.CommunityMember),
}
}
response.ChatsModified[chatID].CategoryModified = chat.CategoryId
}
}
// Check for removed token permissions
for id := range o.config.CommunityDescription.TokenPermissions {
if _, ok := description.TokenPermissions[id]; !ok {
if response.TokenPermissionsRemoved == nil {
response.TokenPermissionsRemoved = make([]string, 0)
}
response.TokenPermissionsRemoved = append(response.TokenPermissionsRemoved, id)
}
}
for id, permission := range description.TokenPermissions {
if _, ok := o.config.CommunityDescription.TokenPermissions[id]; !ok {
if response.TokenPermissionsAdded == nil {
response.TokenPermissionsAdded = make(map[string]*protobuf.CommunityTokenPermission)
}
response.TokenPermissionsAdded[id] = permission
}
}
}
return response, nil
}
func (o *Community) addTokenPermission(permission *protobuf.CommunityTokenPermission) (*CommunityChanges, error) { func (o *Community) addTokenPermission(permission *protobuf.CommunityTokenPermission) (*CommunityChanges, error) {
if o.config.CommunityDescription.TokenPermissions == nil { if o.config.CommunityDescription.TokenPermissions == nil {
o.config.CommunityDescription.TokenPermissions = make(map[string]*protobuf.CommunityTokenPermission) o.config.CommunityDescription.TokenPermissions = make(map[string]*protobuf.CommunityTokenPermission)
@ -2268,10 +2130,15 @@ func (o *Community) updateTokenPermission(permission *protobuf.CommunityTokenPer
} }
func (o *Community) deleteTokenPermission(permissionID string) (*CommunityChanges, error) { func (o *Community) deleteTokenPermission(permissionID string) (*CommunityChanges, error) {
permission, exists := o.config.CommunityDescription.TokenPermissions[permissionID]
if !exists {
return nil, ErrTokenPermissionNotFound
}
delete(o.config.CommunityDescription.TokenPermissions, permissionID) delete(o.config.CommunityDescription.TokenPermissions, permissionID)
changes := o.emptyCommunityChanges() changes := o.emptyCommunityChanges()
changes.TokenPermissionsRemoved = append(changes.TokenPermissionsRemoved, permissionID) changes.TokenPermissionsRemoved[permissionID] = permission
return changes, nil return changes, nil
} }

View File

@ -12,13 +12,14 @@ type CommunityChatChanges struct {
} }
type CommunityChanges struct { type CommunityChanges struct {
Community *Community `json:"community"` Community *Community `json:"community"`
MembersAdded map[string]*protobuf.CommunityMember `json:"membersAdded"` MembersAdded map[string]*protobuf.CommunityMember `json:"membersAdded"`
MembersRemoved map[string]*protobuf.CommunityMember `json:"membersRemoved"` MembersRemoved map[string]*protobuf.CommunityMember `json:"membersRemoved"`
TokenPermissionsAdded map[string]*protobuf.CommunityTokenPermission `json:"tokenPermissionsAdded"` TokenPermissionsAdded map[string]*protobuf.CommunityTokenPermission `json:"tokenPermissionsAdded"`
TokenPermissionsModified map[string]*protobuf.CommunityTokenPermission `json:"tokenPermissionsModified"` TokenPermissionsModified map[string]*protobuf.CommunityTokenPermission `json:"tokenPermissionsModified"`
TokenPermissionsRemoved []string `json:"tokenPermissionsRemoved"` TokenPermissionsRemoved map[string]*protobuf.CommunityTokenPermission `json:"tokenPermissionsRemoved"`
ChatsRemoved map[string]*protobuf.CommunityChat `json:"chatsRemoved"` ChatsRemoved map[string]*protobuf.CommunityChat `json:"chatsRemoved"`
ChatsAdded map[string]*protobuf.CommunityChat `json:"chatsAdded"` ChatsAdded map[string]*protobuf.CommunityChat `json:"chatsAdded"`
@ -45,6 +46,10 @@ func EmptyCommunityChanges() *CommunityChanges {
MembersAdded: make(map[string]*protobuf.CommunityMember), MembersAdded: make(map[string]*protobuf.CommunityMember),
MembersRemoved: make(map[string]*protobuf.CommunityMember), MembersRemoved: make(map[string]*protobuf.CommunityMember),
TokenPermissionsAdded: make(map[string]*protobuf.CommunityTokenPermission),
TokenPermissionsModified: make(map[string]*protobuf.CommunityTokenPermission),
TokenPermissionsRemoved: make(map[string]*protobuf.CommunityTokenPermission),
ChatsRemoved: make(map[string]*protobuf.CommunityChat), ChatsRemoved: make(map[string]*protobuf.CommunityChat),
ChatsAdded: make(map[string]*protobuf.CommunityChat), ChatsAdded: make(map[string]*protobuf.CommunityChat),
ChatsModified: make(map[string]*CommunityChatChanges), ChatsModified: make(map[string]*CommunityChatChanges),
@ -73,3 +78,170 @@ func (c *CommunityChanges) HasMemberLeft(identity string) bool {
_, ok := c.MembersRemoved[identity] _, ok := c.MembersRemoved[identity]
return ok return ok
} }
func EvaluateCommunityChanges(origin, modified *protobuf.CommunityDescription) *CommunityChanges {
changes := EmptyCommunityChanges()
// Check for new members at the org level
for pk, member := range modified.Members {
if _, ok := origin.Members[pk]; !ok {
if changes.MembersAdded == nil {
changes.MembersAdded = make(map[string]*protobuf.CommunityMember)
}
changes.MembersAdded[pk] = member
}
}
// Check for removed members at the org level
for pk, member := range origin.Members {
if _, ok := modified.Members[pk]; !ok {
if changes.MembersRemoved == nil {
changes.MembersRemoved = make(map[string]*protobuf.CommunityMember)
}
changes.MembersRemoved[pk] = member
}
}
// check for removed chats
for chatID, chat := range origin.Chats {
if modified.Chats == nil {
modified.Chats = make(map[string]*protobuf.CommunityChat)
}
if _, ok := modified.Chats[chatID]; !ok {
if changes.ChatsRemoved == nil {
changes.ChatsRemoved = make(map[string]*protobuf.CommunityChat)
}
changes.ChatsRemoved[chatID] = chat
}
}
for chatID, chat := range modified.Chats {
if origin.Chats == nil {
origin.Chats = make(map[string]*protobuf.CommunityChat)
}
if _, ok := origin.Chats[chatID]; !ok {
if changes.ChatsAdded == nil {
changes.ChatsAdded = make(map[string]*protobuf.CommunityChat)
}
changes.ChatsAdded[chatID] = chat
} else {
// Check for members added
for pk, member := range modified.Chats[chatID].Members {
if _, ok := origin.Chats[chatID].Members[pk]; !ok {
if changes.ChatsModified[chatID] == nil {
changes.ChatsModified[chatID] = &CommunityChatChanges{
MembersAdded: make(map[string]*protobuf.CommunityMember),
MembersRemoved: make(map[string]*protobuf.CommunityMember),
}
}
changes.ChatsModified[chatID].MembersAdded[pk] = member
}
}
// check for members removed
for pk, member := range origin.Chats[chatID].Members {
if _, ok := modified.Chats[chatID].Members[pk]; !ok {
if changes.ChatsModified[chatID] == nil {
changes.ChatsModified[chatID] = &CommunityChatChanges{
MembersAdded: make(map[string]*protobuf.CommunityMember),
MembersRemoved: make(map[string]*protobuf.CommunityMember),
}
}
changes.ChatsModified[chatID].MembersRemoved[pk] = member
}
}
// check if first message timestamp was modified
if origin.Chats[chatID].Identity.FirstMessageTimestamp !=
modified.Chats[chatID].Identity.FirstMessageTimestamp {
if changes.ChatsModified[chatID] == nil {
changes.ChatsModified[chatID] = &CommunityChatChanges{
MembersAdded: make(map[string]*protobuf.CommunityMember),
MembersRemoved: make(map[string]*protobuf.CommunityMember),
}
}
changes.ChatsModified[chatID].FirstMessageTimestampModified = modified.Chats[chatID].Identity.FirstMessageTimestamp
}
}
}
// Check for categories that were removed
for categoryID := range origin.Categories {
if modified.Categories == nil {
modified.Categories = make(map[string]*protobuf.CommunityCategory)
}
if modified.Chats == nil {
modified.Chats = make(map[string]*protobuf.CommunityChat)
}
if _, ok := modified.Categories[categoryID]; !ok {
changes.CategoriesRemoved = append(changes.CategoriesRemoved, categoryID)
}
if origin.Chats == nil {
origin.Chats = make(map[string]*protobuf.CommunityChat)
}
}
// Check for categories that were added
for categoryID, category := range modified.Categories {
if origin.Categories == nil {
origin.Categories = make(map[string]*protobuf.CommunityCategory)
}
if _, ok := origin.Categories[categoryID]; !ok {
if changes.CategoriesAdded == nil {
changes.CategoriesAdded = make(map[string]*protobuf.CommunityCategory)
}
changes.CategoriesAdded[categoryID] = category
} else {
if origin.Categories[categoryID].Name != category.Name || origin.Categories[categoryID].Position != category.Position {
changes.CategoriesModified[categoryID] = category
}
}
}
// Check for chat categories that were modified
for chatID, chat := range modified.Chats {
if origin.Chats == nil {
origin.Chats = make(map[string]*protobuf.CommunityChat)
}
if _, ok := origin.Chats[chatID]; !ok {
continue // It's a new chat
}
if origin.Chats[chatID].CategoryId != chat.CategoryId {
if changes.ChatsModified[chatID] == nil {
changes.ChatsModified[chatID] = &CommunityChatChanges{
MembersAdded: make(map[string]*protobuf.CommunityMember),
MembersRemoved: make(map[string]*protobuf.CommunityMember),
}
}
changes.ChatsModified[chatID].CategoryModified = chat.CategoryId
}
}
// Check for removed token permissions
for id, permission := range origin.TokenPermissions {
if _, ok := modified.TokenPermissions[id]; !ok {
changes.TokenPermissionsRemoved[id] = permission
}
}
// Check for added token permissions
for id, permission := range modified.TokenPermissions {
if _, ok := origin.TokenPermissions[id]; !ok {
changes.TokenPermissionsAdded[id] = permission
}
}
return changes
}

View File

@ -0,0 +1,114 @@
package communities
import "github.com/status-im/status-go/protocol/protobuf"
type EncryptionKeyActionType int
const (
EncryptionKeyNone EncryptionKeyActionType = iota
EncryptionKeyAdd
EncryptionKeyRemove
EncryptionKeyRekey
EncryptionKeySendToMembers
)
type EncryptionKeyAction struct {
ActionType EncryptionKeyActionType
Members map[string]*protobuf.CommunityMember
}
type EncryptionKeyActions struct {
// community-level encryption key action
CommunityKeyAction EncryptionKeyAction
// channel-level encryption key actions
ChannelKeysActions map[string]EncryptionKeyAction // key is: chatID
}
func EvaluateCommunityEncryptionKeyActions(origin, modified *Community) *EncryptionKeyActions {
changes := EvaluateCommunityChanges(origin.Description(), modified.Description())
result := &EncryptionKeyActions{
CommunityKeyAction: *evaluateCommunityLevelEncryptionKeyAction(origin, modified, changes),
ChannelKeysActions: *evaluateChannelLevelEncryptionKeyActions(origin, modified, changes),
}
return result
}
func evaluateCommunityLevelEncryptionKeyAction(origin, modified *Community, changes *CommunityChanges) *EncryptionKeyAction {
originBecomeMemberPermissions := origin.TokenPermissionsByType(protobuf.CommunityTokenPermission_BECOME_MEMBER)
modifiedBecomeMemberPermissions := modified.TokenPermissionsByType(protobuf.CommunityTokenPermission_BECOME_MEMBER)
return evaluateEncryptionKeyAction(originBecomeMemberPermissions, modifiedBecomeMemberPermissions, modified.config.CommunityDescription.Members, changes.MembersAdded, changes.MembersRemoved)
}
func evaluateChannelLevelEncryptionKeyActions(origin, modified *Community, changes *CommunityChanges) *map[string]EncryptionKeyAction {
result := make(map[string]EncryptionKeyAction)
for chatID := range modified.config.CommunityDescription.Chats {
originChannelViewOnlyPermissions := origin.ChannelTokenPermissionsByType(chatID, protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL)
originChannelViewAndPostPermissions := origin.ChannelTokenPermissionsByType(chatID, protobuf.CommunityTokenPermission_CAN_VIEW_AND_POST_CHANNEL)
originChannelPermissions := append(originChannelViewOnlyPermissions, originChannelViewAndPostPermissions...)
modifiedChannelViewOnlyPermissions := modified.ChannelTokenPermissionsByType(chatID, protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL)
modifiedChannelViewAndPostPermissions := modified.ChannelTokenPermissionsByType(chatID, protobuf.CommunityTokenPermission_CAN_VIEW_AND_POST_CHANNEL)
modifiedChannelPermissions := append(modifiedChannelViewOnlyPermissions, modifiedChannelViewAndPostPermissions...)
membersAdded := make(map[string]*protobuf.CommunityMember)
membersRemoved := make(map[string]*protobuf.CommunityMember)
chatChanges, ok := changes.ChatsModified[chatID]
if ok {
membersAdded = chatChanges.MembersAdded
membersRemoved = chatChanges.MembersRemoved
}
result[chatID] = *evaluateEncryptionKeyAction(originChannelPermissions, modifiedChannelPermissions, modified.config.CommunityDescription.Members, membersAdded, membersRemoved)
}
return &result
}
func evaluateEncryptionKeyAction(originPermissions, modifiedPermissions []*protobuf.CommunityTokenPermission, allMembers, membersAdded, membersRemoved map[string]*protobuf.CommunityMember) *EncryptionKeyAction {
result := &EncryptionKeyAction{
ActionType: EncryptionKeyNone,
Members: map[string]*protobuf.CommunityMember{},
}
copyMap := func(source map[string]*protobuf.CommunityMember) map[string]*protobuf.CommunityMember {
to := make(map[string]*protobuf.CommunityMember)
for pubKey, member := range source {
to[pubKey] = member
}
return to
}
// permission was just added
if len(modifiedPermissions) > 0 && len(originPermissions) == 0 {
result.ActionType = EncryptionKeyAdd
result.Members = copyMap(allMembers)
return result
}
// permission was just removed
if len(modifiedPermissions) == 0 && len(originPermissions) > 0 {
result.ActionType = EncryptionKeyRemove
result.Members = copyMap(allMembers)
return result
}
// open community/channel does not require any actions
if len(modifiedPermissions) == 0 {
return result
}
if len(membersRemoved) > 0 {
result.ActionType = EncryptionKeyRekey
result.Members = copyMap(allMembers)
} else if len(membersAdded) > 0 {
result.ActionType = EncryptionKeySendToMembers
result.Members = copyMap(membersAdded)
}
return result
}

View File

@ -0,0 +1,781 @@
package communities
import (
"crypto/ecdsa"
"sync"
"testing"
"github.com/stretchr/testify/suite"
"github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/protocol/common"
"github.com/status-im/status-go/protocol/protobuf"
)
func createTestCommunity(identity *ecdsa.PrivateKey) *Community {
return &Community{
config: &Config{
PrivateKey: identity,
CommunityDescription: &protobuf.CommunityDescription{
Members: map[string]*protobuf.CommunityMember{},
Permissions: &protobuf.CommunityPermissions{},
Identity: &protobuf.ChatIdentity{},
Chats: map[string]*protobuf.CommunityChat{},
BanList: []string{},
Categories: map[string]*protobuf.CommunityCategory{},
Encrypted: false,
TokenPermissions: map[string]*protobuf.CommunityTokenPermission{},
CommunityTokensMetadata: []*protobuf.CommunityTokenMetadata{},
},
ID: &identity.PublicKey,
Joined: true,
MemberIdentity: &identity.PublicKey,
},
mutex: sync.Mutex{},
}
}
func TestCommunityEncryptionKeyActionSuite(t *testing.T) {
suite.Run(t, new(CommunityEncryptionKeyActionSuite))
}
type CommunityEncryptionKeyActionSuite struct {
suite.Suite
identity *ecdsa.PrivateKey
communityID []byte
member1 *ecdsa.PrivateKey
member2 *ecdsa.PrivateKey
member3 *ecdsa.PrivateKey
member1Key string
member2Key string
member3Key string
}
func (s *CommunityEncryptionKeyActionSuite) SetupTest() {
identity, err := crypto.GenerateKey()
s.Require().NoError(err)
s.identity = identity
s.communityID = crypto.CompressPubkey(&identity.PublicKey)
member1, err := crypto.GenerateKey()
s.Require().NoError(err)
s.member1 = member1
member2, err := crypto.GenerateKey()
s.Require().NoError(err)
s.member2 = member2
member3, err := crypto.GenerateKey()
s.Require().NoError(err)
s.member3 = member3
s.member1Key = common.PubkeyToHex(&s.member1.PublicKey)
s.member2Key = common.PubkeyToHex(&s.member2.PublicKey)
s.member3Key = common.PubkeyToHex(&s.member3.PublicKey)
}
func (s *CommunityEncryptionKeyActionSuite) TestEncryptionKeyNone() {
origin := createTestCommunity(s.identity)
// if there are no changes there should be no actions
actions := EvaluateCommunityEncryptionKeyActions(origin, origin)
s.Require().Equal(actions.CommunityKeyAction.ActionType, EncryptionKeyNone)
s.Require().Len(actions.ChannelKeysActions, 0)
}
func (s *CommunityEncryptionKeyActionSuite) TestCommunityLevelKeyActions_PermissionsCombinations() {
testCases := []struct {
name string
originPermissions []*protobuf.CommunityTokenPermission
modifiedPermissions []*protobuf.CommunityTokenPermission
expectedActionType EncryptionKeyActionType
}{
{
name: "add member permission",
originPermissions: []*protobuf.CommunityTokenPermission{},
modifiedPermissions: []*protobuf.CommunityTokenPermission{
&protobuf.CommunityTokenPermission{
Id: "some-id",
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
TokenCriteria: make([]*protobuf.TokenCriteria, 0),
ChatIds: []string{},
},
},
expectedActionType: EncryptionKeyAdd,
},
{
name: "add member permissions",
originPermissions: []*protobuf.CommunityTokenPermission{},
modifiedPermissions: []*protobuf.CommunityTokenPermission{
&protobuf.CommunityTokenPermission{
Id: "some-id-1",
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
TokenCriteria: make([]*protobuf.TokenCriteria, 0),
ChatIds: []string{},
},
&protobuf.CommunityTokenPermission{
Id: "some-id-2",
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
TokenCriteria: make([]*protobuf.TokenCriteria, 0),
ChatIds: []string{},
},
},
expectedActionType: EncryptionKeyAdd,
},
{
name: "add another member permission",
originPermissions: []*protobuf.CommunityTokenPermission{
&protobuf.CommunityTokenPermission{
Id: "some-id-1",
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
TokenCriteria: make([]*protobuf.TokenCriteria, 0),
ChatIds: []string{},
},
},
modifiedPermissions: []*protobuf.CommunityTokenPermission{
&protobuf.CommunityTokenPermission{
Id: "some-id-1",
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
TokenCriteria: make([]*protobuf.TokenCriteria, 0),
ChatIds: []string{},
},
&protobuf.CommunityTokenPermission{
Id: "some-id-2",
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
TokenCriteria: make([]*protobuf.TokenCriteria, 0),
ChatIds: []string{},
},
},
expectedActionType: EncryptionKeyNone,
},
{
name: "add another member permission and remove previous one",
originPermissions: []*protobuf.CommunityTokenPermission{
&protobuf.CommunityTokenPermission{
Id: "some-id-1",
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
TokenCriteria: make([]*protobuf.TokenCriteria, 0),
ChatIds: []string{},
},
},
modifiedPermissions: []*protobuf.CommunityTokenPermission{
&protobuf.CommunityTokenPermission{
Id: "some-id-2",
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
TokenCriteria: make([]*protobuf.TokenCriteria, 0),
ChatIds: []string{},
},
},
expectedActionType: EncryptionKeyNone,
},
{
name: "remove member permission",
originPermissions: []*protobuf.CommunityTokenPermission{
&protobuf.CommunityTokenPermission{
Id: "some-id",
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
TokenCriteria: make([]*protobuf.TokenCriteria, 0),
ChatIds: []string{},
},
},
modifiedPermissions: []*protobuf.CommunityTokenPermission{},
expectedActionType: EncryptionKeyRemove,
},
{
name: "remove one of member permissions",
originPermissions: []*protobuf.CommunityTokenPermission{
&protobuf.CommunityTokenPermission{
Id: "some-id-1",
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
TokenCriteria: make([]*protobuf.TokenCriteria, 0),
ChatIds: []string{},
},
&protobuf.CommunityTokenPermission{
Id: "some-id-2",
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
TokenCriteria: make([]*protobuf.TokenCriteria, 0),
ChatIds: []string{},
},
},
modifiedPermissions: []*protobuf.CommunityTokenPermission{
&protobuf.CommunityTokenPermission{
Id: "some-id-1",
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
TokenCriteria: make([]*protobuf.TokenCriteria, 0),
ChatIds: []string{},
},
},
expectedActionType: EncryptionKeyNone,
},
{
name: "add channel permission",
originPermissions: []*protobuf.CommunityTokenPermission{},
modifiedPermissions: []*protobuf.CommunityTokenPermission{
&protobuf.CommunityTokenPermission{
Id: "some-id",
Type: protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL,
TokenCriteria: make([]*protobuf.TokenCriteria, 0),
ChatIds: []string{"some-chat-id"},
},
},
expectedActionType: EncryptionKeyNone,
},
{
name: "remove channel permission",
originPermissions: []*protobuf.CommunityTokenPermission{
&protobuf.CommunityTokenPermission{
Id: "some-id",
Type: protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL,
TokenCriteria: make([]*protobuf.TokenCriteria, 0),
ChatIds: []string{"some-chat-id"},
},
},
modifiedPermissions: []*protobuf.CommunityTokenPermission{},
expectedActionType: EncryptionKeyNone,
},
{
name: "add member permission on top of channel permission",
originPermissions: []*protobuf.CommunityTokenPermission{
&protobuf.CommunityTokenPermission{
Id: "some-id-1",
Type: protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL,
TokenCriteria: make([]*protobuf.TokenCriteria, 0),
ChatIds: []string{"some-chat-id"},
},
},
modifiedPermissions: []*protobuf.CommunityTokenPermission{
&protobuf.CommunityTokenPermission{
Id: "some-id-1",
Type: protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL,
TokenCriteria: make([]*protobuf.TokenCriteria, 0),
ChatIds: []string{"some-chat-id"},
},
&protobuf.CommunityTokenPermission{
Id: "some-id-2",
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
TokenCriteria: make([]*protobuf.TokenCriteria, 0),
ChatIds: []string{""},
},
},
expectedActionType: EncryptionKeyAdd,
},
{
name: "add channel permission on top of member permission",
originPermissions: []*protobuf.CommunityTokenPermission{
&protobuf.CommunityTokenPermission{
Id: "some-id-1",
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
TokenCriteria: make([]*protobuf.TokenCriteria, 0),
ChatIds: []string{""},
},
},
modifiedPermissions: []*protobuf.CommunityTokenPermission{
&protobuf.CommunityTokenPermission{
Id: "some-id-1",
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
TokenCriteria: make([]*protobuf.TokenCriteria, 0),
ChatIds: []string{""},
},
&protobuf.CommunityTokenPermission{
Id: "some-id-2",
Type: protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL,
TokenCriteria: make([]*protobuf.TokenCriteria, 0),
ChatIds: []string{"some-chat-id"},
},
},
expectedActionType: EncryptionKeyNone,
},
{
name: "change member permission to channel permission",
originPermissions: []*protobuf.CommunityTokenPermission{
&protobuf.CommunityTokenPermission{
Id: "some-id",
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
TokenCriteria: make([]*protobuf.TokenCriteria, 0),
ChatIds: []string{""},
},
},
modifiedPermissions: []*protobuf.CommunityTokenPermission{
&protobuf.CommunityTokenPermission{
Id: "some-id",
Type: protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL,
TokenCriteria: make([]*protobuf.TokenCriteria, 0),
ChatIds: []string{""},
},
},
expectedActionType: EncryptionKeyRemove,
},
{
name: "change channel permission to member permission",
originPermissions: []*protobuf.CommunityTokenPermission{
&protobuf.CommunityTokenPermission{
Id: "some-id",
Type: protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL,
TokenCriteria: make([]*protobuf.TokenCriteria, 0),
ChatIds: []string{""},
},
},
modifiedPermissions: []*protobuf.CommunityTokenPermission{
&protobuf.CommunityTokenPermission{
Id: "some-id",
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
TokenCriteria: make([]*protobuf.TokenCriteria, 0),
ChatIds: []string{""},
},
},
expectedActionType: EncryptionKeyAdd,
},
{
name: "change channel permission to member permission on top of member permission",
originPermissions: []*protobuf.CommunityTokenPermission{
&protobuf.CommunityTokenPermission{
Id: "some-id-1",
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
TokenCriteria: make([]*protobuf.TokenCriteria, 0),
ChatIds: []string{""},
},
&protobuf.CommunityTokenPermission{
Id: "some-id-2",
Type: protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL,
TokenCriteria: make([]*protobuf.TokenCriteria, 0),
ChatIds: []string{""},
},
},
modifiedPermissions: []*protobuf.CommunityTokenPermission{
&protobuf.CommunityTokenPermission{
Id: "some-id-1",
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
TokenCriteria: make([]*protobuf.TokenCriteria, 0),
ChatIds: []string{""},
},
&protobuf.CommunityTokenPermission{
Id: "some-id-2",
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
TokenCriteria: make([]*protobuf.TokenCriteria, 0),
ChatIds: []string{""},
},
},
expectedActionType: EncryptionKeyNone,
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
origin := createTestCommunity(s.identity)
modified := origin.createDeepCopy()
for _, permission := range tc.originPermissions {
_, err := origin.AddTokenPermission(permission)
s.Require().NoError(err)
}
for _, permission := range tc.modifiedPermissions {
_, err := modified.AddTokenPermission(permission)
s.Require().NoError(err)
}
actions := EvaluateCommunityEncryptionKeyActions(origin, modified)
s.Require().Equal(tc.expectedActionType, actions.CommunityKeyAction.ActionType)
})
}
}
func (s *CommunityEncryptionKeyActionSuite) TestCommunityLevelKeyActions_MembersCombinations() {
testCases := []struct {
name string
permissions []*protobuf.CommunityTokenPermission
originMembers []*ecdsa.PublicKey
modifiedMembers []*ecdsa.PublicKey
expectedAction EncryptionKeyAction
}{
{
name: "add member to open community",
permissions: []*protobuf.CommunityTokenPermission{},
originMembers: []*ecdsa.PublicKey{},
modifiedMembers: []*ecdsa.PublicKey{&s.member1.PublicKey},
expectedAction: EncryptionKeyAction{
ActionType: EncryptionKeyNone,
Members: map[string]*protobuf.CommunityMember{},
},
},
{
name: "remove member from open community",
permissions: []*protobuf.CommunityTokenPermission{},
originMembers: []*ecdsa.PublicKey{&s.member1.PublicKey},
modifiedMembers: []*ecdsa.PublicKey{},
expectedAction: EncryptionKeyAction{
ActionType: EncryptionKeyNone,
Members: map[string]*protobuf.CommunityMember{},
},
},
{
name: "add member to token-gated community",
permissions: []*protobuf.CommunityTokenPermission{
&protobuf.CommunityTokenPermission{
Id: "some-id",
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
TokenCriteria: make([]*protobuf.TokenCriteria, 0),
ChatIds: []string{},
},
},
originMembers: []*ecdsa.PublicKey{},
modifiedMembers: []*ecdsa.PublicKey{&s.member1.PublicKey},
expectedAction: EncryptionKeyAction{
ActionType: EncryptionKeySendToMembers,
Members: map[string]*protobuf.CommunityMember{
s.member1Key: &protobuf.CommunityMember{},
},
},
},
{
name: "add multiple members to token-gated community",
permissions: []*protobuf.CommunityTokenPermission{
&protobuf.CommunityTokenPermission{
Id: "some-id",
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
TokenCriteria: make([]*protobuf.TokenCriteria, 0),
ChatIds: []string{},
},
},
originMembers: []*ecdsa.PublicKey{},
modifiedMembers: []*ecdsa.PublicKey{&s.member1.PublicKey, &s.member2.PublicKey},
expectedAction: EncryptionKeyAction{
ActionType: EncryptionKeySendToMembers,
Members: map[string]*protobuf.CommunityMember{
s.member1Key: &protobuf.CommunityMember{},
s.member2Key: &protobuf.CommunityMember{},
},
},
},
{
name: "remove member from token-gated community",
permissions: []*protobuf.CommunityTokenPermission{
&protobuf.CommunityTokenPermission{
Id: "some-id",
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
TokenCriteria: make([]*protobuf.TokenCriteria, 0),
ChatIds: []string{},
},
},
originMembers: []*ecdsa.PublicKey{&s.member1.PublicKey, &s.member2.PublicKey},
modifiedMembers: []*ecdsa.PublicKey{&s.member1.PublicKey},
expectedAction: EncryptionKeyAction{
ActionType: EncryptionKeyRekey,
Members: map[string]*protobuf.CommunityMember{
s.member1Key: &protobuf.CommunityMember{},
},
},
},
{
name: "add and remove members from token-gated community",
permissions: []*protobuf.CommunityTokenPermission{
&protobuf.CommunityTokenPermission{
Id: "some-id",
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
TokenCriteria: make([]*protobuf.TokenCriteria, 0),
ChatIds: []string{},
},
},
originMembers: []*ecdsa.PublicKey{&s.member1.PublicKey},
modifiedMembers: []*ecdsa.PublicKey{&s.member2.PublicKey, &s.member3.PublicKey},
expectedAction: EncryptionKeyAction{
ActionType: EncryptionKeyRekey,
Members: map[string]*protobuf.CommunityMember{
s.member2Key: &protobuf.CommunityMember{},
s.member3Key: &protobuf.CommunityMember{},
},
},
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
origin := createTestCommunity(s.identity)
for _, permission := range tc.permissions {
_, err := origin.AddTokenPermission(permission)
s.Require().NoError(err)
}
modified := origin.createDeepCopy()
for _, member := range tc.originMembers {
_, err := origin.AddMember(member, []protobuf.CommunityMember_Roles{})
s.Require().NoError(err)
}
for _, member := range tc.modifiedMembers {
_, err := modified.AddMember(member, []protobuf.CommunityMember_Roles{})
s.Require().NoError(err)
}
actions := EvaluateCommunityEncryptionKeyActions(origin, modified)
s.Require().Equal(tc.expectedAction.ActionType, actions.CommunityKeyAction.ActionType)
s.Require().Len(tc.expectedAction.Members, len(actions.CommunityKeyAction.Members))
for memberKey := range tc.expectedAction.Members {
_, exists := actions.CommunityKeyAction.Members[memberKey]
s.Require().True(exists)
}
})
}
}
func (s *CommunityEncryptionKeyActionSuite) TestCommunityLevelKeyActions_PermissionsMembersCombinations() {
testCases := []struct {
name string
originPermissions []*protobuf.CommunityTokenPermission
modifiedPermissions []*protobuf.CommunityTokenPermission
originMembers []*ecdsa.PublicKey
modifiedMembers []*ecdsa.PublicKey
expectedActionType EncryptionKeyActionType
}{
{
name: "add member permission, add members",
originPermissions: []*protobuf.CommunityTokenPermission{},
modifiedPermissions: []*protobuf.CommunityTokenPermission{
&protobuf.CommunityTokenPermission{
Id: "some-id",
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
TokenCriteria: make([]*protobuf.TokenCriteria, 0),
ChatIds: []string{},
},
},
originMembers: []*ecdsa.PublicKey{},
modifiedMembers: []*ecdsa.PublicKey{&s.member1.PublicKey},
expectedActionType: EncryptionKeyAdd,
},
{
name: "add member permission, remove members",
originPermissions: []*protobuf.CommunityTokenPermission{},
modifiedPermissions: []*protobuf.CommunityTokenPermission{
&protobuf.CommunityTokenPermission{
Id: "some-id",
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
TokenCriteria: make([]*protobuf.TokenCriteria, 0),
ChatIds: []string{},
},
},
originMembers: []*ecdsa.PublicKey{&s.member1.PublicKey},
modifiedMembers: []*ecdsa.PublicKey{},
expectedActionType: EncryptionKeyAdd,
},
{
name: "remove member permission, add members",
originPermissions: []*protobuf.CommunityTokenPermission{
&protobuf.CommunityTokenPermission{
Id: "some-id",
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
TokenCriteria: make([]*protobuf.TokenCriteria, 0),
ChatIds: []string{},
},
},
modifiedPermissions: []*protobuf.CommunityTokenPermission{},
originMembers: []*ecdsa.PublicKey{},
modifiedMembers: []*ecdsa.PublicKey{&s.member1.PublicKey},
expectedActionType: EncryptionKeyRemove,
},
{
name: "remove member permission, remove members",
originPermissions: []*protobuf.CommunityTokenPermission{
&protobuf.CommunityTokenPermission{
Id: "some-id",
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
TokenCriteria: make([]*protobuf.TokenCriteria, 0),
ChatIds: []string{},
},
},
modifiedPermissions: []*protobuf.CommunityTokenPermission{},
originMembers: []*ecdsa.PublicKey{&s.member1.PublicKey},
modifiedMembers: []*ecdsa.PublicKey{},
expectedActionType: EncryptionKeyRemove,
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
origin := createTestCommunity(s.identity)
modified := origin.createDeepCopy()
for _, permission := range tc.originPermissions {
_, err := origin.AddTokenPermission(permission)
s.Require().NoError(err)
}
for _, member := range tc.originMembers {
_, err := origin.AddMember(member, []protobuf.CommunityMember_Roles{})
s.Require().NoError(err)
}
for _, permission := range tc.modifiedPermissions {
_, err := modified.AddTokenPermission(permission)
s.Require().NoError(err)
}
for _, member := range tc.modifiedMembers {
_, err := modified.AddMember(member, []protobuf.CommunityMember_Roles{})
s.Require().NoError(err)
}
actions := EvaluateCommunityEncryptionKeyActions(origin, modified)
s.Require().Equal(tc.expectedActionType, actions.CommunityKeyAction.ActionType)
})
}
}
func (s *CommunityEncryptionKeyActionSuite) TestChannelLevelKeyActions() {
chatID := "0x1234"
testCases := []struct {
name string
originPermissions []*protobuf.CommunityTokenPermission
modifiedPermissions []*protobuf.CommunityTokenPermission
originMembers []*ecdsa.PublicKey
modifiedMembers []*ecdsa.PublicKey
expectedAction EncryptionKeyAction
}{
{
name: "add channel permission",
originPermissions: []*protobuf.CommunityTokenPermission{},
modifiedPermissions: []*protobuf.CommunityTokenPermission{
&protobuf.CommunityTokenPermission{
Id: "some-id",
Type: protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL,
TokenCriteria: make([]*protobuf.TokenCriteria, 0),
ChatIds: []string{chatID},
},
},
originMembers: []*ecdsa.PublicKey{},
modifiedMembers: []*ecdsa.PublicKey{},
expectedAction: EncryptionKeyAction{
ActionType: EncryptionKeyAdd,
Members: map[string]*protobuf.CommunityMember{},
},
},
{
name: "remove channel permission",
originPermissions: []*protobuf.CommunityTokenPermission{
&protobuf.CommunityTokenPermission{
Id: "some-id",
Type: protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL,
TokenCriteria: make([]*protobuf.TokenCriteria, 0),
ChatIds: []string{chatID},
},
},
modifiedPermissions: []*protobuf.CommunityTokenPermission{},
originMembers: []*ecdsa.PublicKey{},
modifiedMembers: []*ecdsa.PublicKey{},
expectedAction: EncryptionKeyAction{
ActionType: EncryptionKeyRemove,
Members: map[string]*protobuf.CommunityMember{},
},
},
{
name: "add members to token-gated channel",
originPermissions: []*protobuf.CommunityTokenPermission{
&protobuf.CommunityTokenPermission{
Id: "some-id",
Type: protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL,
TokenCriteria: make([]*protobuf.TokenCriteria, 0),
ChatIds: []string{chatID},
},
},
modifiedPermissions: []*protobuf.CommunityTokenPermission{
&protobuf.CommunityTokenPermission{
Id: "some-id",
Type: protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL,
TokenCriteria: make([]*protobuf.TokenCriteria, 0),
ChatIds: []string{chatID},
},
},
originMembers: []*ecdsa.PublicKey{},
modifiedMembers: []*ecdsa.PublicKey{&s.member1.PublicKey, &s.member2.PublicKey},
expectedAction: EncryptionKeyAction{
ActionType: EncryptionKeySendToMembers,
Members: map[string]*protobuf.CommunityMember{
s.member1Key: &protobuf.CommunityMember{},
s.member2Key: &protobuf.CommunityMember{},
},
},
},
{
name: "remove members from token-gated channel",
originPermissions: []*protobuf.CommunityTokenPermission{
&protobuf.CommunityTokenPermission{
Id: "some-id",
Type: protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL,
TokenCriteria: make([]*protobuf.TokenCriteria, 0),
ChatIds: []string{chatID},
},
},
modifiedPermissions: []*protobuf.CommunityTokenPermission{
&protobuf.CommunityTokenPermission{
Id: "some-id",
Type: protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL,
TokenCriteria: make([]*protobuf.TokenCriteria, 0),
ChatIds: []string{chatID},
},
},
originMembers: []*ecdsa.PublicKey{&s.member1.PublicKey, &s.member2.PublicKey},
modifiedMembers: []*ecdsa.PublicKey{},
expectedAction: EncryptionKeyAction{
ActionType: EncryptionKeyRekey,
Members: map[string]*protobuf.CommunityMember{},
},
},
{
name: "add members to open channel",
originPermissions: []*protobuf.CommunityTokenPermission{},
modifiedPermissions: []*protobuf.CommunityTokenPermission{},
originMembers: []*ecdsa.PublicKey{},
modifiedMembers: []*ecdsa.PublicKey{&s.member1.PublicKey, &s.member2.PublicKey},
expectedAction: EncryptionKeyAction{
ActionType: EncryptionKeyNone,
Members: map[string]*protobuf.CommunityMember{},
},
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
origin := createTestCommunity(s.identity)
_, err := origin.CreateChat(chatID, &protobuf.CommunityChat{
Members: map[string]*protobuf.CommunityMember{},
Permissions: &protobuf.CommunityPermissions{Access: protobuf.CommunityPermissions_NO_MEMBERSHIP},
Identity: &protobuf.ChatIdentity{},
})
s.Require().NoError(err)
modified := origin.createDeepCopy()
for _, permission := range tc.originPermissions {
_, err := origin.AddTokenPermission(permission)
s.Require().NoError(err)
}
for _, member := range tc.originMembers {
_, err := origin.AddMember(member, []protobuf.CommunityMember_Roles{})
s.Require().NoError(err)
_, err = origin.AddMemberToChat(chatID, member, []protobuf.CommunityMember_Roles{})
s.Require().NoError(err)
}
for _, permission := range tc.modifiedPermissions {
_, err := modified.AddTokenPermission(permission)
s.Require().NoError(err)
}
for _, member := range tc.modifiedMembers {
_, err := modified.AddMember(member, []protobuf.CommunityMember_Roles{})
s.Require().NoError(err)
_, err = modified.AddMemberToChat(chatID, member, []protobuf.CommunityMember_Roles{})
s.Require().NoError(err)
}
actions := EvaluateCommunityEncryptionKeyActions(origin, modified)
channelAction, ok := actions.ChannelKeysActions[chatID]
s.Require().True(ok)
s.Require().Equal(tc.expectedAction.ActionType, channelAction.ActionType)
s.Require().Len(tc.expectedAction.Members, len(channelAction.Members))
for memberKey := range tc.expectedAction.Members {
_, exists := channelAction.Members[memberKey]
s.Require().True(exists)
}
})
}
}

View File

@ -199,12 +199,9 @@ func (o *Community) UpdateCommunityByEvents(communityEventMessage *CommunityEven
return nil, err return nil, err
} }
// Collect `CommunityChanges` data by searching a difference between `CommunityDescrption` // Evaluate `CommunityChanges` data by searching a difference between `CommunityDescription`
// from the DB and `CommunityDescrption` patched by community events // from the DB and `CommunityDescription` patched by community events
changes, err := o.collectCommunityChanges(copy.config.CommunityDescription) changes := EvaluateCommunityChanges(o.config.CommunityDescription, copy.config.CommunityDescription)
if err != nil {
return nil, err
}
// TODO: need to figure out is it ok to save marshaledCommunityDescription without the signature // TODO: need to figure out is it ok to save marshaledCommunityDescription without the signature
marshaledCommDescr, err := proto.Marshal(copy.config.CommunityDescription) marshaledCommDescr, err := proto.Marshal(copy.config.CommunityDescription)