status-im/status-go

View on GitHub
protocol/communities/community_encryption_key_action.go

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
package communities

import (
    "github.com/status-im/status-go/protocol/protobuf"
)

type KeyDistributor interface {
    Generate(community *Community, keyActions *EncryptionKeyActions) error
    Distribute(community *Community, keyActions *EncryptionKeyActions) error
}

type EncryptionKeyActionType int

const (
    EncryptionKeyNone EncryptionKeyActionType = iota
    EncryptionKeyAdd
    EncryptionKeyRemove
    EncryptionKeyRekey
    EncryptionKeySendToMembers
)

type EncryptionKeyAction struct {
    ActionType     EncryptionKeyActionType
    Members        map[string]*protobuf.CommunityMember
    RemovedMembers 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 {
    if origin == nil {
        // `modified` is a new community, create empty `origin` community
        origin = &Community{
            config: &Config{
                ID: modified.config.ID,
                CommunityDescription: &protobuf.CommunityDescription{
                    Members:                 map[string]*protobuf.CommunityMember{},
                    Permissions:             &protobuf.CommunityPermissions{},
                    Identity:                &protobuf.ChatIdentity{},
                    Chats:                   map[string]*protobuf.CommunityChat{},
                    Categories:              map[string]*protobuf.CommunityCategory{},
                    AdminSettings:           &protobuf.CommunityAdminSettings{},
                    TokenPermissions:        map[string]*protobuf.CommunityTokenPermission{},
                    CommunityTokensMetadata: []*protobuf.CommunityTokenMetadata{},
                },
            },
        }
    }

    changes := EvaluateCommunityChanges(origin, modified)

    result := &EncryptionKeyActions{
        CommunityKeyAction: *evaluateCommunityLevelEncryptionKeyAction(origin, modified, changes),
        ChannelKeysActions: *evaluateChannelLevelEncryptionKeyActions(origin, modified, changes),
    }
    return result
}

func evaluateCommunityLevelEncryptionKeyAction(origin, modified *Community, changes *CommunityChanges) *EncryptionKeyAction {
    return evaluateEncryptionKeyAction(
        origin.Encrypted(),
        modified.Encrypted(),
        changes.ControlNodeChanged != nil,
        modified.config.CommunityDescription.Members,
        changes.MembersAdded,
        changes.MembersRemoved,
    )
}

func evaluateChannelLevelEncryptionKeyActions(origin, modified *Community, changes *CommunityChanges) *map[string]EncryptionKeyAction {
    result := make(map[string]EncryptionKeyAction)

    for channelID := range modified.config.CommunityDescription.Chats {
        membersAdded := make(map[string]*protobuf.CommunityMember)
        membersRemoved := make(map[string]*protobuf.CommunityMember)

        chatChanges, ok := changes.ChatsModified[channelID]
        if ok {
            membersAdded = chatChanges.MembersAdded
            membersRemoved = chatChanges.MembersRemoved
        }

        result[channelID] = *evaluateEncryptionKeyAction(
            origin.ChannelEncrypted(channelID),
            modified.ChannelEncrypted(channelID),
            changes.ControlNodeChanged != nil,
            modified.config.CommunityDescription.Chats[channelID].Members,
            membersAdded,
            membersRemoved,
        )
    }

    return &result
}

func evaluateEncryptionKeyAction(originEncrypted, modifiedEncrypted, controlNodeChanged bool,
    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
    }

    // control node changed on closed community/channel
    if controlNodeChanged && modifiedEncrypted {
        result.ActionType = EncryptionKeyRekey
        result.Members = copyMap(allMembers)
        return result
    }

    // encryption was just added
    if modifiedEncrypted && !originEncrypted {
        result.ActionType = EncryptionKeyAdd
        result.Members = copyMap(allMembers)
        return result
    }

    // encryption was just removed
    if !modifiedEncrypted && originEncrypted {
        result.ActionType = EncryptionKeyRemove
        result.Members = copyMap(allMembers)
        return result
    }

    // open community/channel does not require any actions
    if !modifiedEncrypted {
        return result
    }

    if len(membersRemoved) > 0 {
        result.ActionType = EncryptionKeyRekey
        result.Members = copyMap(allMembers)
        result.RemovedMembers = copyMap(membersRemoved)
    } else if len(membersAdded) > 0 {
        result.ActionType = EncryptionKeySendToMembers
        result.Members = copyMap(membersAdded)
    }

    return result
}