Skip to content

Commit

Permalink
feat: introduce channel-level encryption
Browse files Browse the repository at this point in the history
  • Loading branch information
osmaczko committed Jul 21, 2023
1 parent 9a889cc commit 3faee08
Show file tree
Hide file tree
Showing 11 changed files with 403 additions and 131 deletions.
4 changes: 2 additions & 2 deletions protocol/common/message_sender.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ func (s *MessageSender) sendCommunity(
// Check if it's a key exchange message. In this case we send it
// to all the recipients
if rawMessage.CommunityKeyExMsgType != KeyExMsgNone {
keyExMessageSpecs, err := s.protocol.GetKeyExMessageSpecs(rawMessage.CommunityID, s.identity, rawMessage.Recipients, rawMessage.CommunityKeyExMsgType == KeyExMsgRekey)
keyExMessageSpecs, err := s.protocol.GetKeyExMessageSpecs(rawMessage.HashRatchetGroupID, s.identity, rawMessage.Recipients, rawMessage.CommunityKeyExMsgType == KeyExMsgRekey)
if err != nil {
return nil, err
}
Expand All @@ -307,7 +307,7 @@ func (s *MessageSender) sendCommunity(

// If it's a chat message, we send it on the community chat topic
if ShouldCommunityMessageBeEncrypted(rawMessage.MessageType) {
messageSpec, err := s.protocol.BuildHashRatchetMessage(rawMessage.CommunityID, wrappedMessage)
messageSpec, err := s.protocol.BuildHashRatchetMessage(rawMessage.HashRatchetGroupID, wrappedMessage)
if err != nil {
return nil, err
}
Expand Down
1 change: 1 addition & 0 deletions protocol/common/raw_message.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,5 @@ type RawMessage struct {
CommunityKeyExMsgType CommKeyExMsgType
Ephemeral bool
BeforeDispatch func(*RawMessage) error
HashRatchetGroupID []byte
}
116 changes: 76 additions & 40 deletions protocol/communities/community.go
Original file line number Diff line number Diff line change
Expand Up @@ -670,7 +670,6 @@ func (o *Community) InviteUserToChat(pk *ecdsa.PublicKey, chatID string) (*proto
}

func (o *Community) getMember(pk *ecdsa.PublicKey) *protobuf.CommunityMember {

key := common.PubkeyToHex(pk)
member := o.config.CommunityDescription.Members[key]
return member
Expand All @@ -680,6 +679,20 @@ func (o *Community) GetMember(pk *ecdsa.PublicKey) *protobuf.CommunityMember {
return o.getMember(pk)
}

func (o *Community) getChatMember(pk *ecdsa.PublicKey, chatID string) *protobuf.CommunityMember {
if !o.hasMember(pk) {
return nil
}

chat, ok := o.config.CommunityDescription.Chats[chatID]
if !ok {
return nil
}

key := common.PubkeyToHex(pk)
return chat.Members[key]
}

func (o *Community) hasMember(pk *ecdsa.PublicKey) bool {

member := o.getMember(pk)
Expand All @@ -703,16 +716,16 @@ func (o *Community) isBanned(pk *ecdsa.PublicKey) bool {
return false
}

func (o *Community) hasMemberPermission(member *protobuf.CommunityMember, permissions map[protobuf.CommunityMember_Roles]bool) bool {
func (o *Community) memberHasRoles(member *protobuf.CommunityMember, roles map[protobuf.CommunityMember_Roles]bool) bool {
for _, r := range member.Roles {
if permissions[r] {
if roles[r] {
return true
}
}
return false
}

func (o *Community) hasPermission(pk *ecdsa.PublicKey, roles map[protobuf.CommunityMember_Roles]bool) bool {
func (o *Community) hasRoles(pk *ecdsa.PublicKey, roles map[protobuf.CommunityMember_Roles]bool) bool {
if pk == nil || o.config == nil || o.config.ID == nil {
return false
}
Expand All @@ -722,7 +735,7 @@ func (o *Community) hasPermission(pk *ecdsa.PublicKey, roles map[protobuf.Commun
return false
}

return o.hasMemberPermission(member, roles)
return o.memberHasRoles(member, roles)
}

func (o *Community) HasMember(pk *ecdsa.PublicKey) bool {
Expand All @@ -735,18 +748,7 @@ func (o *Community) IsMemberInChat(pk *ecdsa.PublicKey, chatID string) bool {
o.mutex.Lock()
defer o.mutex.Unlock()

if !o.hasMember(pk) {
return false
}

chat, ok := o.config.CommunityDescription.Chats[chatID]
if !ok {
return false
}

key := common.PubkeyToHex(pk)
_, ok = chat.Members[key]
return ok
return o.getChatMember(pk, chatID) != nil
}

func (o *Community) RemoveUserFromChat(pk *ecdsa.PublicKey, chatID string) (*protobuf.CommunityDescription, error) {
Expand Down Expand Up @@ -904,17 +906,27 @@ func (o *Community) AddRoleToMember(pk *ecdsa.PublicKey, role protobuf.Community
}

updated := false
member := o.getMember(pk)
if member != nil {
addRole := func(member *protobuf.CommunityMember) {
roles := make(map[protobuf.CommunityMember_Roles]bool)
roles[role] = true
if !o.hasMemberPermission(member, roles) {
if !o.memberHasRoles(member, roles) {
member.Roles = append(member.Roles, role)
o.config.CommunityDescription.Members[common.PubkeyToHex(pk)] = member
updated = true
}
}

member := o.getMember(pk)
if member != nil {
addRole(member)
}

for channelID := range o.Chats() {
chatMember := o.getChatMember(pk, channelID)
if chatMember != nil {
addRole(member)
}
}

if updated {
o.increaseClock()
}
Expand All @@ -930,23 +942,33 @@ func (o *Community) RemoveRoleFromMember(pk *ecdsa.PublicKey, role protobuf.Comm
}

updated := false
member := o.getMember(pk)
if member != nil {
removeRole := func(member *protobuf.CommunityMember) {
roles := make(map[protobuf.CommunityMember_Roles]bool)
roles[role] = true
if o.hasMemberPermission(member, roles) {
if o.memberHasRoles(member, roles) {
var newRoles []protobuf.CommunityMember_Roles
for _, r := range member.Roles {
if r != role {
newRoles = append(newRoles, r)
}
}
member.Roles = newRoles
o.config.CommunityDescription.Members[common.PubkeyToHex(pk)] = member
updated = true
}
}

member := o.getMember(pk)
if member != nil {
removeRole(member)
}

for channelID := range o.Chats() {
chatMember := o.getChatMember(pk, channelID)
if chatMember != nil {
removeRole(member)
}
}

if updated {
o.increaseClock()
}
Expand Down Expand Up @@ -1131,36 +1153,36 @@ func (o *Community) IsOwnerOrAdmin() bool {
}

func (o *Community) IsMemberOwner(publicKey *ecdsa.PublicKey) bool {
return o.hasPermission(publicKey, ownerRolePermission())
return o.hasRoles(publicKey, ownerRoles())
}

func (o *Community) IsMemberAdmin(publicKey *ecdsa.PublicKey) bool {
return o.hasPermission(publicKey, adminRolePermissions())
return o.hasRoles(publicKey, adminRoles())
}

func (o *Community) IsMemberOwnerOrAdmin(publicKey *ecdsa.PublicKey) bool {
return o.hasPermission(publicKey, ownerOrAdminRolePermissions())
return o.hasRoles(publicKey, ownerOrAdminRoles())
}

func canManageUsersRolePermissions() map[protobuf.CommunityMember_Roles]bool {
roles := ownerOrAdminRolePermissions()
func canManageUsersRoles() map[protobuf.CommunityMember_Roles]bool {
roles := ownerOrAdminRoles()
roles[protobuf.CommunityMember_ROLE_MANAGE_USERS] = true
return roles
}

func ownerRolePermission() map[protobuf.CommunityMember_Roles]bool {
func ownerRoles() map[protobuf.CommunityMember_Roles]bool {
roles := make(map[protobuf.CommunityMember_Roles]bool)
roles[protobuf.CommunityMember_ROLE_OWNER] = true
return roles
}

func adminRolePermissions() map[protobuf.CommunityMember_Roles]bool {
func adminRoles() map[protobuf.CommunityMember_Roles]bool {
roles := make(map[protobuf.CommunityMember_Roles]bool)
roles[protobuf.CommunityMember_ROLE_ADMIN] = true
return roles
}

func ownerOrAdminRolePermissions() map[protobuf.CommunityMember_Roles]bool {
func ownerOrAdminRoles() map[protobuf.CommunityMember_Roles]bool {
roles := make(map[protobuf.CommunityMember_Roles]bool)
roles[protobuf.CommunityMember_ROLE_OWNER] = true
roles[protobuf.CommunityMember_ROLE_ADMIN] = true
Expand All @@ -1182,7 +1204,7 @@ func (o *Community) MemberRole(pubKey *ecdsa.PublicKey) protobuf.CommunityMember
}

func canDeleteMessageForEveryonePermissions() map[protobuf.CommunityMember_Roles]bool {
roles := ownerOrAdminRolePermissions()
roles := ownerOrAdminRoles()
roles[protobuf.CommunityMember_ROLE_MODERATE_CONTENT] = true
return roles
}
Expand Down Expand Up @@ -1392,7 +1414,21 @@ func (o *Community) TokenPermissions() map[string]*protobuf.CommunityTokenPermis
}

func (o *Community) HasTokenPermissions() bool {
return len(o.config.CommunityDescription.TokenPermissions) > 0
return o.config.CommunityDescription.TokenPermissions != nil && len(o.config.CommunityDescription.TokenPermissions) > 0
}

func (o *Community) ChannelHasTokenPermissions(chatID string) bool {
if !o.HasTokenPermissions() {
return false
}

for _, tokenPermission := range o.TokenPermissions() {
if includes(tokenPermission.ChatIds, chatID) {
return true
}
}

return false
}

func TokenPermissionsByType(permissions map[string]*protobuf.CommunityTokenPermission, permissionType protobuf.CommunityTokenPermission_Type) []*protobuf.CommunityTokenPermission {
Expand Down Expand Up @@ -1731,8 +1767,8 @@ func (o *Community) CanManageUsers(pk *ecdsa.PublicKey) bool {
return false
}

roles := canManageUsersRolePermissions()
return o.hasPermission(pk, roles)
roles := canManageUsersRoles()
return o.hasRoles(pk, roles)

}

Expand All @@ -1749,7 +1785,7 @@ func (o *Community) CanDeleteMessageForEveryone(pk *ecdsa.PublicKey) bool {
}

roles := canDeleteMessageForEveryonePermissions()
return o.hasPermission(pk, roles)
return o.hasRoles(pk, roles)
}

func (o *Community) isMember() bool {
Expand Down Expand Up @@ -1787,9 +1823,9 @@ func (o *Community) nextClock() uint64 {

func (o *Community) CanManageUsersPublicKeys() ([]*ecdsa.PublicKey, error) {
var response []*ecdsa.PublicKey
roles := canManageUsersRolePermissions()
roles := canManageUsersRoles()
for pkString, member := range o.config.CommunityDescription.Members {
if o.hasMemberPermission(member, roles) {
if o.memberHasRoles(member, roles) {
pk, err := common.HexToPubkey(pkString)
if err != nil {
return nil, err
Expand Down
14 changes: 7 additions & 7 deletions protocol/communities/community_encryption_key_action.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,25 +63,25 @@ func evaluateCommunityLevelEncryptionKeyAction(origin, modified *Community, chan
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)
for channelID := range modified.config.CommunityDescription.Chats {
originChannelViewOnlyPermissions := origin.ChannelTokenPermissionsByType(modified.IDString()+channelID, protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL)
originChannelViewAndPostPermissions := origin.ChannelTokenPermissionsByType(modified.IDString()+channelID, 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)
modifiedChannelViewOnlyPermissions := modified.ChannelTokenPermissionsByType(modified.IDString()+channelID, protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL)
modifiedChannelViewAndPostPermissions := modified.ChannelTokenPermissionsByType(modified.IDString()+channelID, 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]
chatChanges, ok := changes.ChatsModified[channelID]
if ok {
membersAdded = chatChanges.MembersAdded
membersRemoved = chatChanges.MembersRemoved
}

result[chatID] = *evaluateEncryptionKeyAction(originChannelPermissions, modifiedChannelPermissions, modified.config.CommunityDescription.Members, membersAdded, membersRemoved)
result[channelID] = *evaluateEncryptionKeyAction(originChannelPermissions, modifiedChannelPermissions, modified.config.CommunityDescription.Chats[channelID].Members, membersAdded, membersRemoved)
}

return &result
Expand Down
12 changes: 7 additions & 5 deletions protocol/communities/community_encryption_key_action_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/stretchr/testify/suite"

"github.com/status-im/status-go/eth-node/crypto"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/protocol/common"
"github.com/status-im/status-go/protocol/protobuf"
)
Expand Down Expand Up @@ -622,7 +623,8 @@ func (s *CommunityEncryptionKeyActionSuite) TestCommunityLevelKeyActions_Permiss
}

func (s *CommunityEncryptionKeyActionSuite) TestChannelLevelKeyActions() {
chatID := "0x1234"
channelID := "1234"
chatID := types.EncodeHex(crypto.CompressPubkey(&s.identity.PublicKey)) + channelID
testCases := []struct {
name string
originPermissions []*protobuf.CommunityTokenPermission
Expand Down Expand Up @@ -736,7 +738,7 @@ func (s *CommunityEncryptionKeyActionSuite) TestChannelLevelKeyActions() {
for _, tc := range testCases {
s.Run(tc.name, func() {
origin := createTestCommunity(s.identity)
_, err := origin.CreateChat(chatID, &protobuf.CommunityChat{
_, err := origin.CreateChat(channelID, &protobuf.CommunityChat{
Members: map[string]*protobuf.CommunityMember{},
Permissions: &protobuf.CommunityPermissions{Access: protobuf.CommunityPermissions_NO_MEMBERSHIP},
Identity: &protobuf.ChatIdentity{},
Expand All @@ -752,7 +754,7 @@ func (s *CommunityEncryptionKeyActionSuite) TestChannelLevelKeyActions() {
for _, member := range tc.originMembers {
_, err := origin.AddMember(member, []protobuf.CommunityMember_Roles{})
s.Require().NoError(err)
_, err = origin.AddMemberToChat(chatID, member, []protobuf.CommunityMember_Roles{})
_, err = origin.AddMemberToChat(channelID, member, []protobuf.CommunityMember_Roles{})
s.Require().NoError(err)
}

Expand All @@ -763,12 +765,12 @@ func (s *CommunityEncryptionKeyActionSuite) TestChannelLevelKeyActions() {
for _, member := range tc.modifiedMembers {
_, err := modified.AddMember(member, []protobuf.CommunityMember_Roles{})
s.Require().NoError(err)
_, err = modified.AddMemberToChat(chatID, member, []protobuf.CommunityMember_Roles{})
_, err = modified.AddMemberToChat(channelID, member, []protobuf.CommunityMember_Roles{})
s.Require().NoError(err)
}

actions := EvaluateCommunityEncryptionKeyActions(origin, modified)
channelAction, ok := actions.ChannelKeysActions[chatID]
channelAction, ok := actions.ChannelKeysActions[channelID]
s.Require().True(ok)
s.Require().Equal(tc.expectedAction.ActionType, channelAction.ActionType)
s.Require().Len(tc.expectedAction.Members, len(channelAction.Members))
Expand Down
12 changes: 6 additions & 6 deletions protocol/communities/community_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,10 @@ func (s *CommunitySuite) TestHasPermission() {
memberKey, err := crypto.GenerateKey()
s.Require().NoError(err)

s.Require().False(community.hasPermission(nil, adminRolePermissions()))
s.Require().False(community.hasRoles(nil, adminRoles()))

// returns false if key is passed, but config is nil
s.Require().False(community.hasPermission(&nonMemberKey.PublicKey, adminRolePermissions()))
s.Require().False(community.hasRoles(&nonMemberKey.PublicKey, adminRoles()))

// returns true if the user is the owner

Expand All @@ -87,16 +87,16 @@ func (s *CommunitySuite) TestHasPermission() {

community.config = &Config{ID: &ownerKey.PublicKey, CommunityDescription: communityDescription}

s.Require().True(community.hasPermission(&ownerKey.PublicKey, ownerRolePermission()))
s.Require().True(community.hasRoles(&ownerKey.PublicKey, ownerRoles()))

// return false if user is not a member
s.Require().False(community.hasPermission(&nonMemberKey.PublicKey, adminRolePermissions()))
s.Require().False(community.hasRoles(&nonMemberKey.PublicKey, adminRoles()))

// return true if user is a member and has permissions
s.Require().True(community.hasPermission(&memberKey.PublicKey, adminRolePermissions()))
s.Require().True(community.hasRoles(&memberKey.PublicKey, adminRoles()))

// return false if user is a member and does not have permissions
s.Require().False(community.hasPermission(&memberKey.PublicKey, ownerRolePermission()))
s.Require().False(community.hasRoles(&memberKey.PublicKey, ownerRoles()))

}

Expand Down
Loading

0 comments on commit 3faee08

Please sign in to comment.