feat: rekey community on control node change

closes: status-im/status-desktop#11963
This commit is contained in:
Patryk Osmaczko 2023-10-18 19:01:02 +02:00 committed by osmaczko
parent 246b68a8c0
commit 3292c1c883
4 changed files with 198 additions and 5 deletions

View File

@ -2207,7 +2207,10 @@ func (o *Community) createChat(chatID string, chat *protobuf.CommunityChat) erro
}
}
chat.Members = o.config.CommunityDescription.Members
chat.Members = make(map[string]*protobuf.CommunityMember)
for pubKey, member := range o.config.CommunityDescription.Members {
chat.Members[pubKey] = member
}
o.config.CommunityDescription.Chats[chatID] = chat

View File

@ -1,6 +1,10 @@
package communities
import "github.com/status-im/status-go/protocol/protobuf"
import (
"crypto/ecdsa"
"github.com/status-im/status-go/protocol/protobuf"
)
type CommunityChatChanges struct {
ChatModified *protobuf.CommunityChat
@ -14,6 +18,8 @@ type CommunityChatChanges struct {
type CommunityChanges struct {
Community *Community `json:"community"`
ControlNodeChanged *ecdsa.PublicKey `json:"controlNodeChanged"`
MembersAdded map[string]*protobuf.CommunityMember `json:"membersAdded"`
MembersRemoved map[string]*protobuf.CommunityMember `json:"membersRemoved"`
@ -82,6 +88,10 @@ func (c *CommunityChanges) HasMemberLeft(identity string) bool {
func EvaluateCommunityChanges(origin, modified *Community) *CommunityChanges {
changes := evaluateCommunityChangesByDescription(origin.Description(), modified.Description())
if origin.ControlNode() != nil && !modified.ControlNode().Equal(origin.ControlNode()) {
changes.ControlNodeChanged = modified.ControlNode()
}
originTokenPermissions := origin.tokenPermissions()
modifiedTokenPermissions := modified.tokenPermissions()

View File

@ -57,7 +57,14 @@ func evaluateCommunityLevelEncryptionKeyAction(origin, modified *Community, chan
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)
return evaluateEncryptionKeyAction(
originBecomeMemberPermissions,
modifiedBecomeMemberPermissions,
modified.config.CommunityDescription.Members,
changes.MembersAdded,
changes.MembersRemoved,
changes.ControlNodeChanged != nil,
)
}
func evaluateChannelLevelEncryptionKeyActions(origin, modified *Community, changes *CommunityChanges) *map[string]EncryptionKeyAction {
@ -83,13 +90,20 @@ func evaluateChannelLevelEncryptionKeyActions(origin, modified *Community, chang
membersRemoved = chatChanges.MembersRemoved
}
result[channelID] = *evaluateEncryptionKeyAction(originChannelPermissions, modifiedChannelPermissions, modified.config.CommunityDescription.Chats[channelID].Members, membersAdded, membersRemoved)
result[channelID] = *evaluateEncryptionKeyAction(
originChannelPermissions,
modifiedChannelPermissions,
modified.config.CommunityDescription.Chats[channelID].Members,
membersAdded, membersRemoved,
changes.ControlNodeChanged != nil,
)
}
return &result
}
func evaluateEncryptionKeyAction(originPermissions, modifiedPermissions []*CommunityTokenPermission, allMembers, membersAdded, membersRemoved map[string]*protobuf.CommunityMember) *EncryptionKeyAction {
func evaluateEncryptionKeyAction(originPermissions, modifiedPermissions []*CommunityTokenPermission,
allMembers, membersAdded, membersRemoved map[string]*protobuf.CommunityMember, controlNodeChanged bool) *EncryptionKeyAction {
result := &EncryptionKeyAction{
ActionType: EncryptionKeyNone,
Members: map[string]*protobuf.CommunityMember{},
@ -103,6 +117,13 @@ func evaluateEncryptionKeyAction(originPermissions, modifiedPermissions []*Commu
return to
}
// control node changed on closed community/channel
if controlNodeChanged && len(modifiedPermissions) > 0 {
result.ActionType = EncryptionKeyRekey
result.Members = copyMap(allMembers)
return result
}
// permission was just added
if len(modifiedPermissions) > 0 && len(originPermissions) == 0 {
result.ActionType = EncryptionKeyAdd

View File

@ -2,6 +2,7 @@ package communities
import (
"crypto/ecdsa"
"reflect"
"testing"
"github.com/stretchr/testify/suite"
@ -26,6 +27,7 @@ func createTestCommunity(identity *ecdsa.PrivateKey) (*Community, error) {
CommunityTokensMetadata: []*protobuf.CommunityTokenMetadata{},
},
ID: &identity.PublicKey,
ControlNode: &identity.PublicKey,
Joined: true,
MemberIdentity: &identity.PublicKey,
}
@ -825,3 +827,160 @@ func (s *CommunityEncryptionKeyActionSuite) TestNilOrigin() {
s.Require().NotNil(actions.ChannelKeysActions[channelID])
s.Require().Equal(actions.ChannelKeysActions[channelID].ActionType, EncryptionKeyAdd)
}
func (s *CommunityEncryptionKeyActionSuite) TestControlNodeChange() {
channelID := "1234"
chatID := types.EncodeHex(crypto.CompressPubkey(&s.identity.PublicKey)) + channelID
testCases := []struct {
name string
permissions []*protobuf.CommunityTokenPermission
members []*ecdsa.PublicKey
channelMembers []*ecdsa.PublicKey
expectedActions EncryptionKeyActions
}{
{
name: "change control node in open community",
permissions: []*protobuf.CommunityTokenPermission{},
members: []*ecdsa.PublicKey{&s.member1.PublicKey, &s.member2.PublicKey},
channelMembers: []*ecdsa.PublicKey{&s.member1.PublicKey},
expectedActions: EncryptionKeyActions{
CommunityKeyAction: EncryptionKeyAction{
ActionType: EncryptionKeyNone,
Members: map[string]*protobuf.CommunityMember{},
},
ChannelKeysActions: map[string]EncryptionKeyAction{
channelID: EncryptionKeyAction{
ActionType: EncryptionKeyNone,
Members: map[string]*protobuf.CommunityMember{},
},
},
},
},
{
name: "change control node in token-gated community",
permissions: []*protobuf.CommunityTokenPermission{
&protobuf.CommunityTokenPermission{
Id: "some-id",
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
TokenCriteria: make([]*protobuf.TokenCriteria, 0),
ChatIds: []string{},
},
},
members: []*ecdsa.PublicKey{&s.member1.PublicKey, &s.member2.PublicKey},
channelMembers: []*ecdsa.PublicKey{&s.member1.PublicKey},
expectedActions: EncryptionKeyActions{
CommunityKeyAction: EncryptionKeyAction{
ActionType: EncryptionKeyRekey,
Members: map[string]*protobuf.CommunityMember{
s.member1Key: &protobuf.CommunityMember{},
s.member2Key: &protobuf.CommunityMember{},
},
},
ChannelKeysActions: map[string]EncryptionKeyAction{
channelID: EncryptionKeyAction{
ActionType: EncryptionKeyNone,
Members: map[string]*protobuf.CommunityMember{},
},
},
},
},
{
name: "change control node in open community with token-gated channel",
permissions: []*protobuf.CommunityTokenPermission{
&protobuf.CommunityTokenPermission{
Id: "some-id",
Type: protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL,
TokenCriteria: make([]*protobuf.TokenCriteria, 0),
ChatIds: []string{chatID},
},
},
members: []*ecdsa.PublicKey{&s.member1.PublicKey, &s.member2.PublicKey},
channelMembers: []*ecdsa.PublicKey{&s.member1.PublicKey},
expectedActions: EncryptionKeyActions{
CommunityKeyAction: EncryptionKeyAction{
ActionType: EncryptionKeyNone,
Members: map[string]*protobuf.CommunityMember{},
},
ChannelKeysActions: map[string]EncryptionKeyAction{
channelID: EncryptionKeyAction{
ActionType: EncryptionKeyRekey,
Members: map[string]*protobuf.CommunityMember{
s.member1Key: &protobuf.CommunityMember{},
},
},
},
},
},
{
name: "change control node in token-gated community with token-gated channel",
permissions: []*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{chatID},
},
},
members: []*ecdsa.PublicKey{&s.member1.PublicKey, &s.member2.PublicKey},
channelMembers: []*ecdsa.PublicKey{&s.member1.PublicKey},
expectedActions: EncryptionKeyActions{
CommunityKeyAction: EncryptionKeyAction{
ActionType: EncryptionKeyRekey,
Members: map[string]*protobuf.CommunityMember{
s.member1Key: &protobuf.CommunityMember{},
s.member2Key: &protobuf.CommunityMember{},
},
},
ChannelKeysActions: map[string]EncryptionKeyAction{
channelID: EncryptionKeyAction{
ActionType: EncryptionKeyRekey,
Members: map[string]*protobuf.CommunityMember{
s.member1Key: &protobuf.CommunityMember{},
},
},
},
},
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
origin, err := createTestCommunity(s.identity)
s.Require().NoError(err)
_, err = origin.CreateChat(channelID, &protobuf.CommunityChat{
Members: map[string]*protobuf.CommunityMember{},
Permissions: &protobuf.CommunityPermissions{Access: protobuf.CommunityPermissions_NO_MEMBERSHIP},
Identity: &protobuf.ChatIdentity{},
})
s.Require().NoError(err)
for _, permission := range tc.permissions {
_, err := origin.UpsertTokenPermission(permission)
s.Require().NoError(err)
}
for _, member := range tc.members {
_, err := origin.AddMember(member, []protobuf.CommunityMember_Roles{})
s.Require().NoError(err)
}
for _, member := range tc.channelMembers {
_, err = origin.AddMemberToChat(channelID, member, []protobuf.CommunityMember_Roles{})
s.Require().NoError(err)
}
// change control node to arbitrary member
modified := origin.CreateDeepCopy()
modified.setControlNode(&s.member1.PublicKey)
actions := EvaluateCommunityEncryptionKeyActions(origin, modified)
s.Require().True(reflect.DeepEqual(tc.expectedActions, *actions))
})
}
}