feat: rekey community on control node change
closes: status-im/status-desktop#11963
This commit is contained in:
parent
246b68a8c0
commit
3292c1c883
|
@ -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
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue