diff --git a/protocol/communities/community.go b/protocol/communities/community.go index 6178c5c5e..3cca1fa88 100644 --- a/protocol/communities/community.go +++ b/protocol/communities/community.go @@ -813,7 +813,7 @@ func (o *Community) RemoveUserFromOrg(pk *ecdsa.PublicKey) (*protobuf.CommunityD return nil, ErrNotAdmin } - if allowedToSendEvents && o.IsMemberOwnerOrAdmin(pk) { + if !isControlNode && o.IsPrivilegedMember(pk) { return nil, ErrCannotRemoveOwnerOrAdmin } @@ -883,7 +883,7 @@ func (o *Community) BanUserFromCommunity(pk *ecdsa.PublicKey) (*protobuf.Communi return nil, ErrNotAdmin } - if allowedToSendEvents && o.IsMemberOwnerOrAdmin(pk) { + if !isControlNode && o.IsPrivilegedMember(pk) { return nil, ErrCannotBanOwnerOrAdmin } @@ -1151,6 +1151,17 @@ func (o *Community) IsOwnerWithoutCommunityKey() bool { return o.config.PrivateKey == nil && o.IsMemberOwner(o.config.MemberIdentity) } +func (o *Community) GetPrivilegedMembers() []*ecdsa.PublicKey { + privilegedMembers := make([]*ecdsa.PublicKey, 0) + members := o.GetMemberPubkeys() + for _, member := range members { + if o.IsPrivilegedMember(member) { + privilegedMembers = append(privilegedMembers, member) + } + } + return privilegedMembers +} + func (o *Community) HasPermissionToSendCommunityEvents() bool { return !o.IsControlNode() && o.hasPermission(o.config.MemberIdentity, manageCommunityRolePermissions()) } @@ -1159,11 +1170,15 @@ func (o *Community) IsMemberOwner(publicKey *ecdsa.PublicKey) bool { return o.hasPermission(publicKey, ownerRolePermission()) } +func (o *Community) IsMemberTokenMaster(publicKey *ecdsa.PublicKey) bool { + return o.hasPermission(publicKey, tokenMasterRolePermissions()) +} + func (o *Community) IsMemberAdmin(publicKey *ecdsa.PublicKey) bool { return o.hasPermission(publicKey, adminRolePermissions()) } -func (o *Community) IsMemberOwnerOrAdmin(publicKey *ecdsa.PublicKey) bool { +func (o *Community) IsPrivilegedMember(publicKey *ecdsa.PublicKey) bool { return o.hasPermission(publicKey, manageCommunityRolePermissions()) } @@ -1193,6 +1208,12 @@ func adminRolePermissions() map[protobuf.CommunityMember_Roles]bool { return roles } +func tokenMasterRolePermissions() map[protobuf.CommunityMember_Roles]bool { + roles := make(map[protobuf.CommunityMember_Roles]bool) + roles[protobuf.CommunityMember_ROLE_TOKEN_MASTER] = true + return roles +} + func (o *Community) MemberRole(pubKey *ecdsa.PublicKey) protobuf.CommunityMember_Roles { if o.IsMemberOwner(pubKey) { return protobuf.CommunityMember_ROLE_OWNER @@ -1213,17 +1234,6 @@ func canDeleteMessageForEveryonePermissions() map[protobuf.CommunityMember_Roles return roles } -func (o *Community) GetMemberAdmins() []*ecdsa.PublicKey { - admins := make([]*ecdsa.PublicKey, 0) - members := o.GetMemberPubkeys() - for _, member := range members { - if o.IsMemberAdmin(member) { - admins = append(admins, member) - } - } - return admins -} - func (o *Community) validateRequestToJoinWithChatID(request *protobuf.CommunityRequestToJoin) error { chat, ok := o.config.CommunityDescription.Chats[request.ChatId] diff --git a/protocol/communities/community_event.go b/protocol/communities/community_event.go index 407c22287..f40374e4c 100644 --- a/protocol/communities/community_event.go +++ b/protocol/communities/community_event.go @@ -341,8 +341,8 @@ func (o *Community) updateCommunityDescriptionByCommunityEvent(communityEvent Co return err } - if o.IsMemberOwnerOrAdmin(pk) { - return errors.New("attempt to kick an owner or admin of the community from the admin side") + if !o.IsControlNode() && o.IsPrivilegedMember(pk) { + return errors.New("attempt to kick an control node or privileged user from non-control node side") } o.removeMemberFromOrg(pk) @@ -353,8 +353,8 @@ func (o *Community) updateCommunityDescriptionByCommunityEvent(communityEvent Co return err } - if o.IsMemberOwnerOrAdmin(pk) { - return errors.New("attempt to ban an owner or admin of the community from the admin side") + if !o.IsControlNode() && o.IsPrivilegedMember(pk) { + return errors.New("attempt to ban an control node or privileged user from non-control node side") } o.banUserFromCommunity(pk) @@ -429,6 +429,10 @@ func validateAndGetEventsMessageCommunityDescription(signedDescription []byte, s return nil, err } + if signer == nil { + return nil, errors.New("CommunityDescription does not contain the control node signature") + } + if !signer.Equal(signerPubkey) { return nil, errors.New("CommunityDescription was not signed by an owner") } diff --git a/protocol/communities/manager.go b/protocol/communities/manager.go index 55d30827b..2d365c006 100644 --- a/protocol/communities/manager.go +++ b/protocol/communities/manager.go @@ -1327,8 +1327,8 @@ func (m *Manager) HandleCommunityEventsMessage(signer *ecdsa.PublicKey, message return nil, ErrOrgNotFound } - if !community.IsMemberAdmin(signer) { - return nil, errors.New("user is not an admin") + if !community.IsPrivilegedMember(signer) { + return nil, errors.New("user has not permissions to send events") } changes, err := community.UpdateCommunityByEvents(adminMessage) @@ -2411,13 +2411,13 @@ func (m *Manager) HandleCommunityRequestToJoinResponse(signer *ecdsa.PublicKey, return nil, err } - isOwnerOrAdminSigner := community.IsMemberOwnerOrAdmin(signer) + isPrivilegedUserSigner := community.IsPrivilegedMember(signer) isControlNodeSigner := common.IsPubKeyEqual(community.PublicKey(), signer) - if !isControlNodeSigner && !isOwnerOrAdminSigner { + if !isControlNodeSigner && !isPrivilegedUserSigner { return nil, ErrNotAuthorized } - _, err = community.UpdateCommunityDescription(request.Community, appMetadataMsg, isOwnerOrAdminSigner && !isControlNodeSigner) + _, err = community.UpdateCommunityDescription(request.Community, appMetadataMsg, isPrivilegedUserSigner && !isControlNodeSigner) if err != nil { return nil, err } diff --git a/protocol/communities_events_owner_without_community_key_test.go b/protocol/communities_events_owner_without_community_key_test.go index 257ed25ac..71e92d0c5 100644 --- a/protocol/communities_events_owner_without_community_key_test.go +++ b/protocol/communities_events_owner_without_community_key_test.go @@ -244,7 +244,7 @@ func (s *OwnerWithoutCommunityKeyCommunityEventsSuite) TestOwnerBanOwnerWithoutC testOwnerBanTheSameRole(s, community) } -func (s *OwnerWithoutCommunityKeyCommunityEventsSuite) TestAdminBanControlNode() { +func (s *OwnerWithoutCommunityKeyCommunityEventsSuite) TestOwnerBanControlNode() { community := setUpCommunityAndRoles(s, protobuf.CommunityMember_ROLE_OWNER) testOwnerBanControlNode(s, community) } diff --git a/protocol/communities_events_utils_test.go b/protocol/communities_events_utils_test.go index 57a74c866..010a34792 100644 --- a/protocol/communities_events_utils_test.go +++ b/protocol/communities_events_utils_test.go @@ -91,8 +91,8 @@ func setUpCommunityAndRoles(base CommunityEventsTestsInterface, role protobuf.Co request := &requests.RequestToJoinCommunity{CommunityID: community.ID()} joinCommunity(suite, community, base.GetControlNode(), base.GetEventSender(), request) + refreshMessengerResponses(base) joinCommunity(suite, community, base.GetControlNode(), base.GetMember(), request) - refreshMessengerResponses(base) // grant permissions to the event sender @@ -344,14 +344,14 @@ func setUpOnRequestCommunityAndRoles(base CommunityEventsTestsInterface, role pr // control node creates a community and chat community := createTestCommunity(base, protobuf.CommunityPermissions_ON_REQUEST) + refreshMessengerResponses(base) + advertiseCommunityTo(s, community, base.GetControlNode(), base.GetEventSender()) advertiseCommunityTo(s, community, base.GetControlNode(), base.GetMember()) - refreshMessengerResponses(base) - joinOnRequestCommunity(s, community, base.GetControlNode(), base.GetEventSender()) + refreshMessengerResponses(base) joinOnRequestCommunity(s, community, base.GetControlNode(), base.GetMember()) - refreshMessengerResponses(base) // grant permissions to event sender diff --git a/protocol/communities_messenger_helpers_test.go b/protocol/communities_messenger_helpers_test.go index 778c63516..8926e0e81 100644 --- a/protocol/communities_messenger_helpers_test.go +++ b/protocol/communities_messenger_helpers_test.go @@ -246,9 +246,20 @@ func joinOnRequestCommunity(s *suite.Suite, community *communities.Community, co "user did not receive request to join response", ) s.Require().NoError(err) + userCommunity, err := user.GetCommunityByID(community.ID()) s.Require().NoError(err) s.Require().True(userCommunity.HasMember(&user.identity.PublicKey)) + + // We can't identify which owner is a control node, so owner will receive twice request to join event + _, err = WaitOnMessengerResponse( + controlNode, + func(r *MessengerResponse) bool { + return len(r.Communities()) > 0 + }, + "user did not receive request to join response", + ) + s.Require().NoError(err) } func sendChatMessage(s *suite.Suite, sender *Messenger, chatID string, text string) *common.Message { @@ -270,7 +281,7 @@ func grantPermission(s *suite.Suite, community *communities.Community, controlNo responseAddRole, err := controlNode.AddRoleToMember(&requests.AddRoleToMember{ CommunityID: community.ID(), User: common.PubkeyToHexBytes(target.IdentityPublicKey()), - Role: protobuf.CommunityMember_ROLE_ADMIN, + Role: role, }) s.Require().NoError(err) @@ -280,14 +291,25 @@ func grantPermission(s *suite.Suite, community *communities.Community, controlNo } rCommunities := response.Communities() s.Require().Len(rCommunities, 1) - s.Require().True(rCommunities[0].IsMemberAdmin(target.IdentityPublicKey())) + switch role { + case protobuf.CommunityMember_ROLE_OWNER: + s.Require().True(rCommunities[0].IsMemberOwner(target.IdentityPublicKey())) + case protobuf.CommunityMember_ROLE_ADMIN: + s.Require().True(rCommunities[0].IsMemberAdmin(target.IdentityPublicKey())) + case protobuf.CommunityMember_ROLE_TOKEN_MASTER: + s.Require().True(rCommunities[0].IsMemberTokenMaster(target.IdentityPublicKey())) + default: + return false + } + return true } - checkRole(responseAddRole) + s.Require().True(checkRole(responseAddRole)) - _, err = WaitOnMessengerResponse(target, func(response *MessengerResponse) bool { - return checkRole(response) + response, err := WaitOnMessengerResponse(target, func(response *MessengerResponse) bool { + return len(response.Communities()) > 0 }, "community description changed message not received") s.Require().NoError(err) + s.Require().True(checkRole(response)) } diff --git a/protocol/messenger_communities.go b/protocol/messenger_communities.go index b5f4b2328..38475cdbf 100644 --- a/protocol/messenger_communities.go +++ b/protocol/messenger_communities.go @@ -935,12 +935,14 @@ func (m *Messenger) RequestToJoinCommunity(request *requests.RequestToJoinCommun return nil, err } - // send request to join also to community admins - communityAdmins := community.GetMemberAdmins() - for _, communityAdmin := range communityAdmins { - _, err := m.sender.SendPrivate(context.Background(), communityAdmin, &rawMessage) - if err != nil { - return nil, err + if !community.AcceptRequestToJoinAutomatically() { + // send request to join also to community privileged members + privilegedMembers := community.GetPrivilegedMembers() + for _, privilegedMember := range privilegedMembers { + _, err := m.sender.SendPrivate(context.Background(), privilegedMember, &rawMessage) + if err != nil { + return nil, err + } } } @@ -1068,10 +1070,10 @@ func (m *Messenger) EditSharedAddressesForCommunity(request *requests.EditShared return nil, err } - // send edit message also to community admins - communityAdmins := community.GetMemberAdmins() - for _, communityAdmin := range communityAdmins { - _, err := m.sender.SendPrivate(context.Background(), communityAdmin, &rawMessage) + // send edit message also to privileged members + privilegedMembers := community.GetPrivilegedMembers() + for _, privilegedMember := range privilegedMembers { + _, err := m.sender.SendPrivate(context.Background(), privilegedMember, &rawMessage) if err != nil { return nil, err } diff --git a/protocol/messenger_handler.go b/protocol/messenger_handler.go index 6e10aa274..4fabbca5c 100644 --- a/protocol/messenger_handler.go +++ b/protocol/messenger_handler.go @@ -2589,10 +2589,10 @@ func (m *Messenger) matchChatEntity(chatEntity common.ChatEntity) (*Chat, error) return nil, err } - isMemberOwnerOrAdmin := community.IsMemberOwnerOrAdmin(chatEntity.GetSigPubKey()) + hasPermission := community.IsPrivilegedMember(chatEntity.GetSigPubKey()) pinMessageAllowed := community.AllowsAllMembersToPinMessage() - if (pinMessage && !isMemberOwnerOrAdmin && !pinMessageAllowed) || (!emojiReaction && !canPost) { + if (pinMessage && !hasPermission && !pinMessageAllowed) || (!emojiReaction && !canPost) { return nil, errors.New("user can't post") } diff --git a/protocol/messenger_pin_messages.go b/protocol/messenger_pin_messages.go index acf3d9d1f..d95fa17c3 100644 --- a/protocol/messenger_pin_messages.go +++ b/protocol/messenger_pin_messages.go @@ -36,10 +36,10 @@ func (m *Messenger) sendPinMessage(ctx context.Context, message *common.PinMessa if err != nil { return nil, err } - isMemberOwnerOrAdmin := community.IsMemberOwnerOrAdmin(&m.identity.PublicKey) + hasPermission := community.IsPrivilegedMember(&m.identity.PublicKey) pinMessageAllowed := community.AllowsAllMembersToPinMessage() - if !pinMessageAllowed && !isMemberOwnerOrAdmin { + if !pinMessageAllowed && !hasPermission { return nil, errors.New("member can't pin message") } }