refactor: EventSenders forward RequestToJoin decision to control node

This is a bigger change in how community membership requests are handled
among admins, token masters, owners, and control nodes.

Prior to this commit, all privileged users, also known as
`EventSenders`, were able to accept and reject community membership
requests and those changes would be applied by all users.

This commit changes this behaviour such that:

1. EventSenders can make a decision (accept, reject), but merely forward
   their decision to the control node, which ultimately has to confirm
   it
2. EventSenders are no longer removing or adding members to and from
   communities
3. When an eventsender signaled a decision, the membership request will
   enter a pending state (acceptedPending or rejectedPending)
4. Once a decision was made by one eventsender, no other eventsender can
   override that decision

This implementation is covered with a bunch of tests:

- Ensure that decision made by event sender is shared with other event
  senders
  - `testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders()`
  - `testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders()`
- Ensure memebrship request stays pending, until control node has
  confirmed decision by event senders
  - `testAcceptMemberRequestToJoinNotConfirmedByControlNode()`
  - `testRejectMemberRequestToJoinNotConfirmedByControlNode()`
- Ensure that decision made by event sender cannot be overriden by other
  event senders
  - `testEventSenderCannotOverrideRequestToJoinState()`

These test cases live in three test suites for different event sender
types respectively

- `OwnerWithoutCommunityKeyCommunityEventsSuite`
- `TokenMasterCommunityEventsSuite`
- `AdminCommunityEventsSuite`

In addition to the changes mentioned above, there's also a smaller
changes that ensures membership requests to *not* attached revealed wallet
addresses when the requests are sent to event senders (in addition to
control nodes).

Requests send to a control node will still include revealed addresses as
the control node needs them to verify token permissions.

This commit does not yet handle the case of event senders attempting to
kick and ban members.

Similar to accepting and rejecting membership requests, kicking and
banning need a new pending state. However, we don't track such state in
local databases yet so those two cases will be handled in future commit
to not have this commit grow larger.
This commit is contained in:
Pascal Precht 2023-08-02 14:04:47 +02:00 committed by r4bbit
parent 57e64122a8
commit 248e4a7f24
13 changed files with 942 additions and 185 deletions

View File

@ -37,6 +37,8 @@ const (
ActivityCenterMembershipStatusPending
ActivityCenterMembershipStatusAccepted
ActivityCenterMembershipStatusDeclined
ActivityCenterMembershipStatusAcceptedPending
ActivityCenterMembershipStatusDeclinedPending
)
type ActivityCenterQueryParamsRead uint

View File

@ -753,7 +753,6 @@ func (o *Community) RemoveUserFromOrg(pk *ecdsa.PublicKey) (*protobuf.CommunityD
}
o.removeMemberFromOrg(pk)
if isControlNode {
o.increaseClock()
}
@ -1940,31 +1939,14 @@ func (o *Community) AddMemberWithRevealedAccounts(dbRequest *RequestToJoin, role
defer o.mutex.Unlock()
isControlNode := o.IsControlNode()
allowedToSendEvents := o.HasPermissionToSendCommunityEvents()
if !isControlNode && !allowedToSendEvents {
if !isControlNode {
return nil, ErrNotAdmin
}
changes := o.addMemberWithRevealedAccounts(dbRequest.PublicKey, roles, accounts, dbRequest.Clock)
if allowedToSendEvents {
acceptedRequestsToJoin := make(map[string]*protobuf.CommunityRequestToJoin)
acceptedRequestsToJoin[dbRequest.PublicKey] = dbRequest.ToCommunityRequestToJoinProtobuf()
adminChanges := &CommunityEventChanges{
CommunityChanges: changes,
AcceptedRequestsToJoin: acceptedRequestsToJoin,
}
err := o.addNewCommunityEvent(o.ToCommunityRequestToJoinAcceptCommunityEvent(adminChanges))
if err != nil {
return nil, err
}
}
if isControlNode {
o.increaseClock()
}
o.increaseClock()
return changes, nil
}
@ -2130,16 +2112,6 @@ func (o *Community) deleteChat(chatID string) *CommunityChanges {
return changes
}
func (o *Community) addCommunityMember(pk *ecdsa.PublicKey, member *protobuf.CommunityMember) {
if o.config.CommunityDescription.Members == nil {
o.config.CommunityDescription.Members = make(map[string]*protobuf.CommunityMember)
}
memberKey := common.PubkeyToHex(pk)
o.config.CommunityDescription.Members[memberKey] = member
}
func (o *Community) addTokenPermission(permission *protobuf.CommunityTokenPermission) (*CommunityChanges, error) {
if o.config.CommunityDescription.TokenPermissions == nil {
o.config.CommunityDescription.TokenPermissions = make(map[string]*protobuf.CommunityTokenPermission)

View File

@ -160,7 +160,6 @@ func (o *Community) ToCommunityRequestToJoinAcceptCommunityEvent(changes *Commun
return &CommunityEvent{
CommunityEventClock: o.NewCommunityEventClock(),
Type: protobuf.CommunityEvent_COMMUNITY_REQUEST_TO_JOIN_ACCEPT,
MembersAdded: changes.MembersAdded,
AcceptedRequestsToJoin: changes.AcceptedRequestsToJoin,
}
}
@ -323,26 +322,6 @@ func (o *Community) updateCommunityDescriptionByCommunityEvent(communityEvent Co
return err
}
case protobuf.CommunityEvent_COMMUNITY_REQUEST_TO_JOIN_ACCEPT:
for pkString, addedMember := range communityEvent.MembersAdded {
pk, err := common.HexToPubkey(pkString)
if err != nil {
return err
}
if !o.HasMember(pk) {
o.addCommunityMember(pk, addedMember)
}
}
case protobuf.CommunityEvent_COMMUNITY_REQUEST_TO_JOIN_REJECT:
for pkString := range communityEvent.RejectedRequestsToJoin {
pk, err := common.HexToPubkey(pkString)
if err != nil {
return err
}
o.removeMemberFromOrg(pk)
}
case protobuf.CommunityEvent_COMMUNITY_MEMBER_KICK:
pk, err := common.HexToPubkey(communityEvent.MemberToAction)
if err != nil {

View File

@ -192,7 +192,7 @@ func validateCommunityEvent(communityEvent *CommunityEvent) error {
}
case protobuf.CommunityEvent_COMMUNITY_REQUEST_TO_JOIN_ACCEPT:
if len(communityEvent.MembersAdded) == 0 {
if communityEvent.AcceptedRequestsToJoin == nil {
return errors.New("invalid community request to join accepted event")
}

View File

@ -287,6 +287,8 @@ type Subscription struct {
DownloadingHistoryArchivesFinishedSignal *signal.DownloadingHistoryArchivesFinishedSignal
ImportingHistoryArchiveMessagesSignal *signal.ImportingHistoryArchiveMessagesSignal
CommunityEventsMessage *CommunityEventsMessage
AcceptedRequestsToJoin []types.HexBytes
RejectedRequestsToJoin []types.HexBytes
}
type CommunityResponse struct {
@ -1353,42 +1355,8 @@ func (m *Manager) HandleCommunityEventsMessage(signer *ecdsa.PublicKey, message
func (m *Manager) handleAdditionalAdminChanges(community *Community) error {
saveOrUpdateRequestToJoin := func(signer string, request *protobuf.CommunityRequestToJoin, state RequestToJoinState) error {
requestToJoin := &RequestToJoin{
PublicKey: signer,
Clock: request.Clock,
ENSName: request.EnsName,
CommunityID: request.CommunityId,
State: state,
RevealedAccounts: request.RevealedAccounts,
}
requestToJoin.CalculateID()
existingRequestToJoin, err := m.persistence.GetRequestToJoin(requestToJoin.ID)
if err != nil && err != sql.ErrNoRows {
return err
}
if existingRequestToJoin != nil {
// node already knows about this request to join, so let's compare clocks
// and update it if necessary
if existingRequestToJoin.Clock <= requestToJoin.Clock {
pk, err := common.HexToPubkey(existingRequestToJoin.PublicKey)
if err != nil {
return err
}
err = m.persistence.SetRequestToJoinState(common.PubkeyToHex(pk), community.ID(), state)
if err != nil {
return err
}
}
} else {
err := m.persistence.SaveRequestToJoin(requestToJoin)
if err != nil {
return err
}
}
if !(community.IsControlNode() || community.HasPermissionToSendCommunityEvents()) {
// we're a normal user/member node, so there's nothing for us to do here
return nil
}
@ -1396,19 +1364,15 @@ func (m *Manager) handleAdditionalAdminChanges(community *Community) error {
communityEvent := &community.config.EventsData.Events[i]
switch communityEvent.Type {
case protobuf.CommunityEvent_COMMUNITY_REQUEST_TO_JOIN_ACCEPT:
for signer, request := range communityEvent.AcceptedRequestsToJoin {
err := saveOrUpdateRequestToJoin(signer, request, RequestToJoinStateAccepted)
if err != nil {
return err
}
err := m.handleCommunityEventRequestAccepted(community, communityEvent)
if err != nil {
return err
}
case protobuf.CommunityEvent_COMMUNITY_REQUEST_TO_JOIN_REJECT:
for signer, request := range communityEvent.RejectedRequestsToJoin {
err := saveOrUpdateRequestToJoin(signer, request, RequestToJoinStateDeclined)
if err != nil {
return err
}
err := m.handleCommunityEventRequestRejected(community, communityEvent)
if err != nil {
return err
}
default:
@ -1417,6 +1381,132 @@ func (m *Manager) handleAdditionalAdminChanges(community *Community) error {
return nil
}
func (m *Manager) saveOrUpdateRequestToJoin(signer string, communityID types.HexBytes, requestToJoin *RequestToJoin) (bool, error) {
updated := false
existingRequestToJoin, err := m.persistence.GetRequestToJoin(requestToJoin.ID)
if err != nil && err != sql.ErrNoRows {
return updated, err
}
if existingRequestToJoin != nil {
// node already knows about this request to join, so let's compare clocks
// and update it if necessary
if existingRequestToJoin.Clock <= requestToJoin.Clock {
pk, err := common.HexToPubkey(existingRequestToJoin.PublicKey)
if err != nil {
return updated, err
}
err = m.persistence.SetRequestToJoinState(common.PubkeyToHex(pk), communityID, requestToJoin.State)
if err != nil {
return updated, err
}
updated = true
}
} else {
err := m.persistence.SaveRequestToJoin(requestToJoin)
if err != nil {
return updated, err
}
}
return updated, nil
}
func (m *Manager) handleCommunityEventRequestAccepted(community *Community, communityEvent *CommunityEvent) error {
requestToJoinState := RequestToJoinStateAccepted
if community.HasPermissionToSendCommunityEvents() {
// if we're an admin and we receive this admin event, we know the state is `pending`
requestToJoinState = RequestToJoinStateAcceptedPending
}
acceptedRequestsToJoin := make([]types.HexBytes, 0)
for signer, request := range communityEvent.AcceptedRequestsToJoin {
requestToJoin := &RequestToJoin{
PublicKey: signer,
Clock: request.Clock,
ENSName: request.EnsName,
CommunityID: request.CommunityId,
State: requestToJoinState,
}
requestToJoin.CalculateID()
if community.HasPermissionToSendCommunityEvents() {
existingRequestToJoin, err := m.persistence.GetRequestToJoin(requestToJoin.ID)
if err != nil && err != sql.ErrNoRows {
return err
}
if existingRequestToJoin.MarkedAsPendingByPrivilegedAccount() {
// the request is already in some pending state so we won't override it again
continue
}
}
requestUpdated, err := m.saveOrUpdateRequestToJoin(signer, community.ID(), requestToJoin)
if err != nil {
return err
}
if community.IsControlNode() && requestUpdated {
// We only collect requestToJoinIDs which had a state update.
// If there wasn't a state update, it means we've seen the request for the first time,
// which means we don't have revealed addresses here (as they aren't propagated by
// admin nodes), so we don't want to trigger an `AcceptRequestToJoin` in such cases.
acceptedRequestsToJoin = append(acceptedRequestsToJoin, requestToJoin.ID)
}
}
if community.IsControlNode() {
m.publish(&Subscription{AcceptedRequestsToJoin: acceptedRequestsToJoin})
}
return nil
}
func (m *Manager) handleCommunityEventRequestRejected(community *Community, communityEvent *CommunityEvent) error {
requestToJoinState := RequestToJoinStateDeclined
if community.HasPermissionToSendCommunityEvents() {
// if we're an admin and we receive this admin event, we want to see the same
// state that the other admin has decided for
requestToJoinState = RequestToJoinStateDeclinedPending
}
rejectedRequestsToJoin := make([]types.HexBytes, 0)
for signer, request := range communityEvent.RejectedRequestsToJoin {
requestToJoin := &RequestToJoin{
PublicKey: signer,
Clock: request.Clock,
ENSName: request.EnsName,
CommunityID: request.CommunityId,
State: requestToJoinState,
}
requestToJoin.CalculateID()
if community.HasPermissionToSendCommunityEvents() {
existingRequestToJoin, err := m.persistence.GetRequestToJoin(requestToJoin.ID)
if err != nil && err != sql.ErrNoRows {
return err
}
if existingRequestToJoin.MarkedAsPendingByPrivilegedAccount() {
// the request is already in some pending state so we won't override it again
continue
}
}
requestUpdated, err := m.saveOrUpdateRequestToJoin(signer, community.ID(), requestToJoin)
if err != nil {
return err
}
if community.IsControlNode() && requestUpdated {
rejectedRequestsToJoin = append(rejectedRequestsToJoin, requestToJoin.ID)
}
}
if community.IsControlNode() {
m.publish(&Subscription{RejectedRequestsToJoin: rejectedRequestsToJoin})
}
return nil
}
// markRequestToJoin marks all the pending requests to join as completed
// if we are members
func (m *Manager) markRequestToJoin(pk *ecdsa.PublicKey, community *Community) error {
@ -1430,6 +1520,10 @@ func (m *Manager) markRequestToJoinAsCanceled(pk *ecdsa.PublicKey, community *Co
return m.persistence.SetRequestToJoinState(common.PubkeyToHex(pk), community.ID(), RequestToJoinStateCanceled)
}
func (m *Manager) markRequestToJoinAsAcceptedPending(pk *ecdsa.PublicKey, community *Community) error {
return m.persistence.SetRequestToJoinState(common.PubkeyToHex(pk), community.ID(), RequestToJoinStateAcceptedPending)
}
func (m *Manager) DeletePendingRequestToJoin(request *RequestToJoin) error {
community, err := m.GetByID(request.CommunityID)
if err != nil {
@ -1575,54 +1669,84 @@ func (m *Manager) AcceptRequestToJoin(request *requests.AcceptRequestToJoinCommu
return nil, err
}
community, err := m.GetByID(dbRequest.CommunityID)
if err != nil {
return nil, err
}
revealedAccounts, err := m.persistence.GetRequestToJoinRevealedAddresses(dbRequest.ID)
if err != nil {
return nil, err
}
permissionsSatisfied, role, err := m.accountsSatisfyPermissionsToJoin(community, revealedAccounts)
if err != nil {
return nil, err
}
if !permissionsSatisfied {
return community, ErrNoPermissionToJoin
}
memberRoles := []protobuf.CommunityMember_Roles{}
if role != protobuf.CommunityMember_ROLE_NONE {
memberRoles = []protobuf.CommunityMember_Roles{role}
}
pk, err := common.HexToPubkey(dbRequest.PublicKey)
if err != nil {
return nil, err
}
_, err = community.AddMemberWithRevealedAccounts(dbRequest, memberRoles, revealedAccounts)
community, err := m.GetByID(dbRequest.CommunityID)
if err != nil {
return nil, err
}
channels, err := m.accountsSatisfyPermissionsToJoinChannels(community, revealedAccounts)
if err != nil {
return nil, err
}
if community.HasPermissionToSendCommunityEvents() {
if dbRequest.MarkedAsPendingByPrivilegedAccount() {
// if the request is in any pending state, it means our admin node has either
// already made a decision in the past, or previously received a decision by
// another admin, which in both cases means we're not allowed to override this
// state again
return nil, errors.New("request to join is already in pending state")
}
for channelID := range channels {
_, err = community.AddMemberToChat(channelID, pk, memberRoles)
// admins do not perform permission checks, they merely mark the
// request as accepted (pending) and forward their decision to the control node
acceptedRequestsToJoin := make(map[string]*protobuf.CommunityRequestToJoin)
acceptedRequestsToJoin[dbRequest.PublicKey] = dbRequest.ToCommunityRequestToJoinProtobuf()
adminChanges := &CommunityEventChanges{
AcceptedRequestsToJoin: acceptedRequestsToJoin,
}
err := community.addNewCommunityEvent(community.ToCommunityRequestToJoinAcceptCommunityEvent(adminChanges))
if err != nil {
return nil, err
}
if err := m.markRequestToJoinAsAcceptedPending(pk, community); err != nil {
return nil, err
}
}
if err := m.markRequestToJoin(pk, community); err != nil {
return nil, err
if community.IsControlNode() {
revealedAccounts, err := m.persistence.GetRequestToJoinRevealedAddresses(dbRequest.ID)
if err != nil {
return nil, err
}
permissionsSatisfied, role, err := m.accountsSatisfyPermissionsToJoin(community, revealedAccounts)
if err != nil {
return nil, err
}
if !permissionsSatisfied {
return community, ErrNoPermissionToJoin
}
memberRoles := []protobuf.CommunityMember_Roles{}
if role != protobuf.CommunityMember_ROLE_NONE {
memberRoles = []protobuf.CommunityMember_Roles{role}
}
_, err = community.AddMemberWithRevealedAccounts(dbRequest, memberRoles, revealedAccounts)
if err != nil {
return nil, err
}
channels, err := m.accountsSatisfyPermissionsToJoinChannels(community, revealedAccounts)
if err != nil {
return nil, err
}
for channelID := range channels {
_, err = community.AddMemberToChat(channelID, pk, memberRoles)
if err != nil {
return nil, err
}
}
if err := m.markRequestToJoin(pk, community); err != nil {
return nil, err
}
}
err = m.saveAndPublish(community)
@ -1648,7 +1772,11 @@ func (m *Manager) DeclineRequestToJoin(request *requests.DeclineRequestToJoinCom
return err
}
err = m.persistence.SetRequestToJoinState(dbRequest.PublicKey, dbRequest.CommunityID, RequestToJoinStateDeclined)
requestToJoinState := RequestToJoinStateDeclined
if community.HasPermissionToSendCommunityEvents() {
requestToJoinState = RequestToJoinStateDeclinedPending
}
err = m.persistence.SetRequestToJoinState(dbRequest.PublicKey, dbRequest.CommunityID, requestToJoinState)
if err != nil {
return err
}
@ -1727,7 +1855,7 @@ func (m *Manager) HandleCommunityRequestToJoin(signer *ecdsa.PublicKey, request
}
// don't process request as admin if community is configured as auto-accept
if community.HasPermissionToSendCommunityEvents() && community.AcceptRequestToJoinAutomatically() {
if !community.IsControlNode() && community.AcceptRequestToJoinAutomatically() {
return nil, errors.New("ignoring request to join, community is set to auto-accept")
}
@ -1759,11 +1887,29 @@ func (m *Manager) HandleCommunityRequestToJoin(signer *ecdsa.PublicKey, request
requestToJoin.CalculateID()
if err := m.persistence.SaveRequestToJoin(requestToJoin); err != nil {
existingRequestToJoin, err := m.persistence.GetRequestToJoin(requestToJoin.ID)
if err != nil && err != sql.ErrNoRows {
return nil, err
}
if len(request.RevealedAccounts) > 0 {
if existingRequestToJoin != nil {
// request to join was already processed by an admin and waits to get
// confirmation for its decision
//
// we're only interested in immediately declining any declined/pending
// requests here, because if it's accepted/pending, we still need to perform
// some checks
if existingRequestToJoin.State == RequestToJoinStateDeclinedPending {
requestToJoin.State = RequestToJoinStateDeclined
return requestToJoin, nil
}
} else {
if err := m.persistence.SaveRequestToJoin(requestToJoin); err != nil {
return nil, err
}
}
if len(request.RevealedAccounts) > 0 && community.IsControlNode() {
// verify if revealed addresses indeed belong to requester
for _, revealedAccount := range request.RevealedAccounts {
recoverParams := account.RecoverParams{
@ -1784,7 +1930,7 @@ func (m *Manager) HandleCommunityRequestToJoin(signer *ecdsa.PublicKey, request
}
// Save revealed addresses + signatures so they can later be added
// to the community member list when the request is accepted
// to the control node's local table of known revealed addresses
err = m.persistence.SaveRequestToJoinRevealedAddresses(requestToJoin)
if err != nil {
return nil, err
@ -1796,22 +1942,42 @@ func (m *Manager) HandleCommunityRequestToJoin(signer *ecdsa.PublicKey, request
// More specifically, CommunityRequestToLeave may be delivered later than CommunityRequestToJoin, or not delivered at all
acceptAutomatically := community.AcceptRequestToJoinAutomatically() || community.HasMember(signer)
if acceptAutomatically {
err = m.markRequestToJoin(signer, community)
if err != nil {
return nil, err
if community.IsControlNode() {
err = m.markRequestToJoin(signer, community)
if err != nil {
return nil, err
}
// Don't check permissions here,
// it will be done further in the processing pipeline.
requestToJoin.State = RequestToJoinStateAccepted
} else {
err = m.markRequestToJoinAsAcceptedPending(signer, community)
if err != nil {
return nil, err
}
requestToJoin.State = RequestToJoinStateAcceptedPending
}
// Don't check permissions here,
// it will be done further in the processing pipeline.
requestToJoin.State = RequestToJoinStateAccepted
return requestToJoin, nil
}
permissionsSatisfied, _, err := m.accountsSatisfyPermissionsToJoin(community, request.RevealedAccounts)
if err != nil {
return nil, err
}
if !permissionsSatisfied {
requestToJoin.State = RequestToJoinStateDeclined
if community.IsControlNode() && len(request.RevealedAccounts) > 0 {
permissionsSatisfied, _, err := m.accountsSatisfyPermissionsToJoin(community, request.RevealedAccounts)
if err != nil {
return nil, err
}
if !permissionsSatisfied {
requestToJoin.State = RequestToJoinStateDeclined
}
if permissionsSatisfied && existingRequestToJoin.State == RequestToJoinStateAcceptedPending {
err = m.markRequestToJoin(signer, community)
if err != nil {
return nil, err
}
// if the request to join was already accepted by another admin,
// we mark it as accepted so it won't be in pending state, even if the community
// is not set to auto-accept
requestToJoin.State = RequestToJoinStateAccepted
}
}
return requestToJoin, nil
@ -2801,6 +2967,14 @@ func (m *Manager) AcceptedRequestsToJoinForCommunity(id types.HexBytes) ([]*Requ
return m.persistence.AcceptedRequestsToJoinForCommunity(id)
}
func (m *Manager) AcceptedPendingRequestsToJoinForCommunity(id types.HexBytes) ([]*RequestToJoin, error) {
return m.persistence.AcceptedPendingRequestsToJoinForCommunity(id)
}
func (m *Manager) DeclinedPendingRequestsToJoinForCommunity(id types.HexBytes) ([]*RequestToJoin, error) {
return m.persistence.DeclinedPendingRequestsToJoinForCommunity(id)
}
func (m *Manager) CanPost(pk *ecdsa.PublicKey, communityID string, chatID string, grant []byte) (bool, error) {
community, err := m.GetByIDString(communityID)
if err != nil {

View File

@ -749,6 +749,14 @@ func (p *Persistence) AcceptedRequestsToJoinForCommunity(id []byte) ([]*RequestT
return p.RequestsToJoinForCommunityWithState(id, RequestToJoinStateAccepted)
}
func (p *Persistence) AcceptedPendingRequestsToJoinForCommunity(id []byte) ([]*RequestToJoin, error) {
return p.RequestsToJoinForCommunityWithState(id, RequestToJoinStateAcceptedPending)
}
func (p *Persistence) DeclinedPendingRequestsToJoinForCommunity(id []byte) ([]*RequestToJoin, error) {
return p.RequestsToJoinForCommunityWithState(id, RequestToJoinStateDeclinedPending)
}
func (p *Persistence) SetRequestToJoinState(pk string, communityID []byte, state RequestToJoinState) error {
_, err := p.db.Exec(`UPDATE communities_requests_to_join SET state = ? WHERE community_id = ? AND public_key = ?`, state, communityID, pk)
return err

View File

@ -16,6 +16,8 @@ const (
RequestToJoinStateDeclined
RequestToJoinStateAccepted
RequestToJoinStateCanceled
RequestToJoinStateAcceptedPending
RequestToJoinStateDeclinedPending
)
type RequestToJoin struct {
@ -72,6 +74,10 @@ func (r *RequestToJoin) Empty() bool {
return len(r.ID)+len(r.PublicKey)+int(r.Clock)+len(r.ENSName)+len(r.ChatID)+len(r.CommunityID)+int(r.State) == 0
}
func (r *RequestToJoin) MarkedAsPendingByPrivilegedAccount() bool {
return r.State == RequestToJoinStateAcceptedPending || r.State == RequestToJoinStateDeclinedPending
}
func AddTimeoutToRequestToJoinClock(clock uint64) (uint64, error) {
requestToJoinClock, err := strconv.ParseInt(fmt.Sprint(clock), 10, 64)
if err != nil {

View File

@ -198,22 +198,70 @@ func (s *OwnerWithoutCommunityKeyCommunityEventsSuite) TestOwnerCannotDeleteBeco
s.Require().Nil(response)
}
func (s *OwnerWithoutCommunityKeyCommunityEventsSuite) TestOwnerAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders() {
additionalOwner := s.newMessenger()
community := setUpOnRequestCommunityAndRoles(s, protobuf.CommunityMember_ROLE_OWNER, []*Messenger{additionalOwner})
// set up additional user that will send request to join
user := s.newMessenger()
testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders(s, community, user, additionalOwner)
}
func (s *OwnerWithoutCommunityKeyCommunityEventsSuite) TestOwnerAcceptMemberRequestToJoinNotConfirmedByControlNode() {
community := setUpOnRequestCommunityAndRoles(s, protobuf.CommunityMember_ROLE_OWNER, []*Messenger{})
// set up additional user that will send request to join
user := s.newMessenger()
testAcceptMemberRequestToJoinNotConfirmedByControlNode(s, community, user)
}
func (s *OwnerWithoutCommunityKeyCommunityEventsSuite) TestOwnerAcceptMemberRequestToJoin() {
community := setUpOnRequestCommunityAndRoles(s, protobuf.CommunityMember_ROLE_OWNER)
community := setUpOnRequestCommunityAndRoles(s, protobuf.CommunityMember_ROLE_OWNER, []*Messenger{})
// set up additional user that will send request to join
user := s.newMessenger()
testAcceptMemberRequestToJoin(s, community, user)
}
func (s *OwnerWithoutCommunityKeyCommunityEventsSuite) TestOwnerRejectMemberRequestToJoinResponseSharedWithOtherEventSenders() {
additionalOwner := s.newMessenger()
community := setUpOnRequestCommunityAndRoles(s, protobuf.CommunityMember_ROLE_OWNER, []*Messenger{additionalOwner})
// set up additional user that will send request to join
user := s.newMessenger()
testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders(s, community, user, additionalOwner)
}
func (s *OwnerWithoutCommunityKeyCommunityEventsSuite) TestOwnerRejectMemberRequestToJoinNotConfirmedByControlNode() {
community := setUpOnRequestCommunityAndRoles(s, protobuf.CommunityMember_ROLE_OWNER, []*Messenger{})
// set up additional user that will send request to join
user := s.newMessenger()
testRejectMemberRequestToJoinNotConfirmedByControlNode(s, community, user)
}
func (s *OwnerWithoutCommunityKeyCommunityEventsSuite) TestOwnerRejectMemberRequestToJoin() {
community := setUpOnRequestCommunityAndRoles(s, protobuf.CommunityMember_ROLE_OWNER)
community := setUpOnRequestCommunityAndRoles(s, protobuf.CommunityMember_ROLE_OWNER, []*Messenger{})
// set up additional user that will send request to join
user := s.newMessenger()
testRejectMemberRequestToJoin(s, community, user)
}
func (s *OwnerWithoutCommunityKeyCommunityEventsSuite) TestOwnerRequestToJoinStateCannotBeOverridden() {
additionalOwner := s.newMessenger()
community := setUpOnRequestCommunityAndRoles(s, protobuf.CommunityMember_ROLE_OWNER, []*Messenger{additionalOwner})
// set up additional user that will send request to join
user := s.newMessenger()
testEventSenderCannotOverrideRequestToJoinState(s, community, user, additionalOwner)
}
func (s *OwnerWithoutCommunityKeyCommunityEventsSuite) TestOwnerControlNodeHandlesMultipleEventSenderRequestToJoinDecisions() {
additionalOwner := s.newMessenger()
community := setUpOnRequestCommunityAndRoles(s, protobuf.CommunityMember_ROLE_OWNER, []*Messenger{additionalOwner})
// set up additional user that will send request to join
user := s.newMessenger()
testControlNodeHandlesMultipleEventSenderRequestToJoinDecisions(s, community, user, additionalOwner)
}
func (s *OwnerWithoutCommunityKeyCommunityEventsSuite) TestOwnerCreateEditDeleteCategories() {
community := setUpCommunityAndRoles(s, protobuf.CommunityMember_ROLE_OWNER)
testCreateEditDeleteCategories(s, community)

View File

@ -124,20 +124,68 @@ func (s *TokenMasterCommunityEventsSuite) TestTokenMasterCannotDeleteBecomeAdmin
testEventSenderCannotDeleteBecomeAdminPermission(s, community)
}
func (s *TokenMasterCommunityEventsSuite) TestTokenMasterAcceptMemberRequestToJoinNotConfirmedByControlNode() {
community := setUpOnRequestCommunityAndRoles(s, protobuf.CommunityMember_ROLE_TOKEN_MASTER, []*Messenger{})
// set up additional user that will send request to join
user := s.newMessenger()
testAcceptMemberRequestToJoinNotConfirmedByControlNode(s, community, user)
}
func (s *TokenMasterCommunityEventsSuite) TestTokenMasterAcceptMemberRequestToJoin() {
community := setUpOnRequestCommunityAndRoles(s, protobuf.CommunityMember_ROLE_TOKEN_MASTER)
community := setUpOnRequestCommunityAndRoles(s, protobuf.CommunityMember_ROLE_TOKEN_MASTER, []*Messenger{})
// set up additional user that will send request to join
user := s.newMessenger()
testAcceptMemberRequestToJoin(s, community, user)
}
func (s *TokenMasterCommunityEventsSuite) TestTokenMasterAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders() {
additionalTokenMaster := s.newMessenger()
community := setUpOnRequestCommunityAndRoles(s, protobuf.CommunityMember_ROLE_TOKEN_MASTER, []*Messenger{additionalTokenMaster})
// set up additional user that will send request to join
user := s.newMessenger()
testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders(s, community, user, additionalTokenMaster)
}
func (s *TokenMasterCommunityEventsSuite) TestTokenMasterRejectMemberRequestToJoinResponseSharedWithOtherEventSenders() {
additionalTokenMaster := s.newMessenger()
community := setUpOnRequestCommunityAndRoles(s, protobuf.CommunityMember_ROLE_TOKEN_MASTER, []*Messenger{additionalTokenMaster})
// set up additional user that will send request to join
user := s.newMessenger()
testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders(s, community, user, additionalTokenMaster)
}
func (s *TokenMasterCommunityEventsSuite) TestTokenMasterRejectMemberRequestToJoinNotConfirmedByControlNode() {
community := setUpOnRequestCommunityAndRoles(s, protobuf.CommunityMember_ROLE_TOKEN_MASTER, []*Messenger{})
// set up additional user that will send request to join
user := s.newMessenger()
testRejectMemberRequestToJoinNotConfirmedByControlNode(s, community, user)
}
func (s *TokenMasterCommunityEventsSuite) TestTokenMasterRejectMemberRequestToJoin() {
community := setUpOnRequestCommunityAndRoles(s, protobuf.CommunityMember_ROLE_TOKEN_MASTER)
community := setUpOnRequestCommunityAndRoles(s, protobuf.CommunityMember_ROLE_TOKEN_MASTER, []*Messenger{})
// set up additional user that will send request to join
user := s.newMessenger()
testRejectMemberRequestToJoin(s, community, user)
}
func (s *TokenMasterCommunityEventsSuite) TestTokenMasterRequestToJoinStateCannotBeOverridden() {
additionalTokenMaster := s.newMessenger()
community := setUpOnRequestCommunityAndRoles(s, protobuf.CommunityMember_ROLE_TOKEN_MASTER, []*Messenger{additionalTokenMaster})
// set up additional user that will send request to join
user := s.newMessenger()
testEventSenderCannotOverrideRequestToJoinState(s, community, user, additionalTokenMaster)
}
func (s *TokenMasterCommunityEventsSuite) TestTokenMasterControlNodeHandlesMultipleEventSenderRequestToJoinDecisions() {
additionalTokenMaster := s.newMessenger()
community := setUpOnRequestCommunityAndRoles(s, protobuf.CommunityMember_ROLE_TOKEN_MASTER, []*Messenger{additionalTokenMaster})
// set up additional user that will send request to join
user := s.newMessenger()
testControlNodeHandlesMultipleEventSenderRequestToJoinDecisions(s, community, user, additionalTokenMaster)
}
func (s *TokenMasterCommunityEventsSuite) TestTokenMasterCreateEditDeleteCategories() {
community := setUpCommunityAndRoles(s, protobuf.CommunityMember_ROLE_TOKEN_MASTER)
testCreateEditDeleteCategories(s, community)

View File

@ -338,7 +338,7 @@ func assertCheckTokenPermissionCreated(s *suite.Suite, community *communities.Co
s.Require().Equal(permissions[0].TokenCriteria[0].Decimals, uint64(18))
}
func setUpOnRequestCommunityAndRoles(base CommunityEventsTestsInterface, role protobuf.CommunityMember_Roles) *communities.Community {
func setUpOnRequestCommunityAndRoles(base CommunityEventsTestsInterface, role protobuf.CommunityMember_Roles, additionalEventSenders []*Messenger) *communities.Community {
tcs2, err := base.GetControlNode().communitiesManager.All()
s := base.GetSuite()
s.Require().NoError(err, "eventSender.communitiesManager.All")
@ -365,9 +365,20 @@ func setUpOnRequestCommunityAndRoles(base CommunityEventsTestsInterface, role pr
checkPermissionGranted := func(response *MessengerResponse) error {
return checkRolePermissionInResponse(response, base.GetEventSender().IdentityPublicKey(), role)
}
waitOnMessengerResponse(s, WaitCommunityCondition, checkPermissionGranted, base.GetMember())
for _, eventSender := range additionalEventSenders {
advertiseCommunityTo(s, community, base.GetControlNode(), eventSender)
joinOnRequestCommunity(s, community, base.GetControlNode(), eventSender)
grantPermission(s, community, base.GetControlNode(), eventSender, role)
checkPermissionGranted = func(response *MessengerResponse) error {
return checkRolePermissionInResponse(response, eventSender.IdentityPublicKey(), role)
}
waitOnMessengerResponse(s, WaitCommunityCondition, checkPermissionGranted, base.GetMember())
waitOnMessengerResponse(s, WaitCommunityCondition, checkPermissionGranted, base.GetEventSender())
}
return community
}
@ -761,7 +772,8 @@ func testAcceptMemberRequestToJoin(base CommunityEventsTestsInterface, community
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.RequestsToJoinCommunity, 1)
_ = response.RequestsToJoinCommunity[0]
sentRequest := response.RequestsToJoinCommunity[0]
// event sender receives request to join
response, err = WaitOnMessengerResponse(
@ -772,19 +784,107 @@ func testAcceptMemberRequestToJoin(base CommunityEventsTestsInterface, community
s.Require().NoError(err)
s.Require().Len(response.RequestsToJoinCommunity, 1)
receivedRequest := response.RequestsToJoinCommunity[0]
// event sender has not accepted request yet
eventSenderCommunity, err := base.GetEventSender().GetCommunityByID(community.ID())
s.Require().NoError(err)
s.Require().False(eventSenderCommunity.HasMember(&user.identity.PublicKey))
acceptRequestToJoin := &requests.AcceptRequestToJoinCommunity{ID: sentRequest.ID}
response, err = base.GetEventSender().AcceptRequestToJoinCommunity(acceptRequestToJoin)
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.Communities(), 1)
// we don't expect `user` to be a member already, because `eventSender` merely
// forwards its accept decision to the control node
s.Require().False(response.Communities()[0].HasMember(&user.identity.PublicKey))
// user receives community admin event without being a member yet
response, err = WaitOnMessengerResponse(
user,
func(r *MessengerResponse) bool { return len(r.Communities()) > 0 },
"user did not receive community request to join response",
)
s.Require().NoError(err)
s.Require().Len(response.Communities(), 1)
// `user` should not be part of the community yet, we need to wait for
// the control node to confirm `eventSender`s decision
s.Require().False(response.Communities()[0].HasMember(&user.identity.PublicKey))
// control node receives community event with accepted membership request
response, err = WaitOnMessengerResponse(
base.GetControlNode(),
func(r *MessengerResponse) bool { return len(r.Communities()) > 0 },
"control node did not receive community request to join response",
)
s.Require().NoError(err)
s.Require().Len(response.Communities(), 1)
// at this point, the request to join is marked as accepted by control node
acceptedRequests, err := base.GetControlNode().AcceptedRequestsToJoinForCommunity(community.ID())
s.Require().NoError(err)
// we expect 3 here (1 event senders, 1 member + 1 from user)
s.Require().Len(acceptedRequests, 3)
s.Require().Equal(acceptedRequests[2].PublicKey, common.PubkeyToHex(&user.identity.PublicKey))
// user receives updated community
_, err = WaitOnMessengerResponse(
user,
func(r *MessengerResponse) bool {
return len(r.Communities()) > 0 && r.Communities()[0].HasMember(&user.identity.PublicKey)
},
"alice did not receive community request to join response",
)
s.Require().NoError(err)
}
func testAcceptMemberRequestToJoinNotConfirmedByControlNode(base CommunityEventsTestsInterface, community *communities.Community, user *Messenger) {
// set up additional user that will send request to join
_, err := user.Start()
s := base.GetSuite()
s.Require().NoError(err)
defer user.Shutdown() // nolint: errcheck
advertiseCommunityTo(s, community, base.GetControlNode(), user)
// user sends request to join
requestToJoin := &requests.RequestToJoinCommunity{CommunityID: community.ID()}
response, err := user.RequestToJoinCommunity(requestToJoin)
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.RequestsToJoinCommunity, 1)
sentRequest := response.RequestsToJoinCommunity[0]
// event sender receives request to join
response, err = WaitOnMessengerResponse(
base.GetEventSender(),
func(r *MessengerResponse) bool { return len(r.RequestsToJoinCommunity) > 0 },
"event sender did not receive community request to join",
)
s.Require().NoError(err)
s.Require().Len(response.RequestsToJoinCommunity, 1)
// event sender has not accepted request yet
eventSenderCommunity, err := base.GetEventSender().GetCommunityByID(community.ID())
s.Require().NoError(err)
s.Require().False(eventSenderCommunity.HasMember(&user.identity.PublicKey))
acceptRequestToJoin := &requests.AcceptRequestToJoinCommunity{ID: receivedRequest.ID}
acceptRequestToJoin := &requests.AcceptRequestToJoinCommunity{ID: sentRequest.ID}
response, err = base.GetEventSender().AcceptRequestToJoinCommunity(acceptRequestToJoin)
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.Communities(), 1)
s.Require().True(response.Communities()[0].HasMember(&user.identity.PublicKey))
// we don't expect `user` to be a member already, because `eventSender` merely
// forwards its accept decision to the control node
s.Require().False(response.Communities()[0].HasMember(&user.identity.PublicKey))
// at this point, the request to join is in accepted/pending state
acceptedPendingRequests, err := base.GetEventSender().AcceptedPendingRequestsToJoinForCommunity(community.ID())
s.Require().NoError(err)
s.Require().Len(acceptedPendingRequests, 1)
s.Require().Equal(acceptedPendingRequests[0].PublicKey, common.PubkeyToHex(&user.identity.PublicKey))
// user receives request to join response
response, err = WaitOnMessengerResponse(
@ -794,32 +894,179 @@ func testAcceptMemberRequestToJoin(base CommunityEventsTestsInterface, community
)
s.Require().NoError(err)
s.Require().Len(response.Communities(), 1)
s.Require().True(response.Communities()[0].HasMember(&user.identity.PublicKey))
// `user` should not be part of the community yet, because control node
// hasn't confirmed the decistion yet
s.Require().False(response.Communities()[0].HasMember(&user.identity.PublicKey))
}
// control node receives updated community
response, err = WaitOnMessengerResponse(
base.GetControlNode(),
func(r *MessengerResponse) bool { return len(r.Communities()) > 0 },
"control node did not receive community request to join response",
func testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders(base CommunityEventsTestsInterface, community *communities.Community, user *Messenger, additionalEventSender *Messenger) {
// set up additional user that will send request to join
_, err := user.Start()
s := base.GetSuite()
s.Require().NoError(err)
defer user.Shutdown() // nolint: errcheck
advertiseCommunityTo(s, community, base.GetControlNode(), user)
// user sends request to join
requestToJoin := &requests.RequestToJoinCommunity{CommunityID: community.ID()}
response, err := user.RequestToJoinCommunity(requestToJoin)
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.RequestsToJoinCommunity, 1)
sentRequest := response.RequestsToJoinCommunity[0]
// event sender receives request to join
_, err = WaitOnMessengerResponse(
base.GetEventSender(),
func(r *MessengerResponse) bool { return len(r.RequestsToJoinCommunity) > 0 },
"event sender did not receive community request to join",
)
s.Require().NoError(err)
s.Require().Len(response.Communities(), 1)
requests, err := base.GetControlNode().AcceptedRequestsToJoinForCommunity(community.ID())
// there's now two requests to join (event sender and member) + 1 from user
s.Require().NoError(err)
s.Require().Len(requests, 3)
s.Require().True(response.Communities()[0].HasMember(&user.identity.PublicKey))
// member receives updated community
response, err = WaitOnMessengerResponse(
base.GetMember(),
func(r *MessengerResponse) bool { return len(r.Communities()) > 0 },
"alice did not receive community request to join response",
// event sender 2 receives request to join
_, err = WaitOnMessengerResponse(
additionalEventSender,
func(r *MessengerResponse) bool { return len(r.RequestsToJoinCommunity) > 0 },
"event sender did not receive community request to join",
)
s.Require().NoError(err)
// event sender 1 accepts request
acceptRequestToJoin := &requests.AcceptRequestToJoinCommunity{ID: sentRequest.ID}
response, err = base.GetEventSender().AcceptRequestToJoinCommunity(acceptRequestToJoin)
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.Communities(), 1)
s.Require().True(response.Communities()[0].HasMember(&user.identity.PublicKey))
// event sender 2 receives decision of other event sender
_, err = WaitOnMessengerResponse(
additionalEventSender,
func(r *MessengerResponse) bool { return len(r.Communities()) > 0 },
"event sender did not receive community request to join",
)
s.Require().NoError(err)
// at this point, the request to join is in accepted/pending state for event sender 2
acceptedPendingRequests, err := additionalEventSender.AcceptedPendingRequestsToJoinForCommunity(community.ID())
s.Require().NoError(err)
s.Require().Len(acceptedPendingRequests, 1)
s.Require().Equal(acceptedPendingRequests[0].PublicKey, common.PubkeyToHex(&user.identity.PublicKey))
}
func testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders(base CommunityEventsTestsInterface, community *communities.Community, user *Messenger, additionalEventSender *Messenger) {
// set up additional user that will send request to join
_, err := user.Start()
s := base.GetSuite()
s.Require().NoError(err)
defer user.Shutdown() // nolint: errcheck
advertiseCommunityTo(s, community, base.GetControlNode(), user)
// user sends request to join
requestToJoin := &requests.RequestToJoinCommunity{CommunityID: community.ID()}
response, err := user.RequestToJoinCommunity(requestToJoin)
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.RequestsToJoinCommunity, 1)
sentRequest := response.RequestsToJoinCommunity[0]
// event sender receives request to join
response, err = WaitOnMessengerResponse(
base.GetEventSender(),
func(r *MessengerResponse) bool { return len(r.RequestsToJoinCommunity) > 0 },
"event sender did not receive community request to join",
)
s.Require().NoError(err)
s.Require().Len(response.RequestsToJoinCommunity, 1)
// event sender 2 receives request to join
response, err = WaitOnMessengerResponse(
additionalEventSender,
func(r *MessengerResponse) bool { return len(r.RequestsToJoinCommunity) > 0 },
"event sender did not receive community request to join",
)
s.Require().NoError(err)
s.Require().Len(response.RequestsToJoinCommunity, 1)
rejectRequestToJoin := &requests.DeclineRequestToJoinCommunity{ID: sentRequest.ID}
response, err = base.GetEventSender().DeclineRequestToJoinCommunity(rejectRequestToJoin)
s.Require().NoError(err)
s.Require().NotNil(response)
// event sender 2 receives decision of other event sender
_, err = WaitOnMessengerResponse(
additionalEventSender,
func(r *MessengerResponse) bool { return len(r.Communities()) > 0 },
"event sender did not receive community request to join",
)
s.Require().NoError(err)
// at this point, the request to join is in declined/pending state for event sender 2
rejectedPendingRequests, err := additionalEventSender.DeclinedPendingRequestsToJoinForCommunity(community.ID())
s.Require().NoError(err)
s.Require().Len(rejectedPendingRequests, 1)
s.Require().Equal(rejectedPendingRequests[0].PublicKey, common.PubkeyToHex(&user.identity.PublicKey))
}
func testRejectMemberRequestToJoinNotConfirmedByControlNode(base CommunityEventsTestsInterface, community *communities.Community, user *Messenger) {
// set up additional user that will send request to join
_, err := user.Start()
s := base.GetSuite()
s.Require().NoError(err)
defer user.Shutdown() // nolint: errcheck
advertiseCommunityTo(s, community, base.GetControlNode(), user)
// user sends request to join
requestToJoin := &requests.RequestToJoinCommunity{CommunityID: community.ID()}
response, err := user.RequestToJoinCommunity(requestToJoin)
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.RequestsToJoinCommunity, 1)
sentRequest := response.RequestsToJoinCommunity[0]
// event sender receives request to join
response, err = WaitOnMessengerResponse(
base.GetEventSender(),
func(r *MessengerResponse) bool { return len(r.RequestsToJoinCommunity) > 0 },
"event sender did not receive community request to join",
)
s.Require().NoError(err)
s.Require().Len(response.RequestsToJoinCommunity, 1)
// event sender has not accepted request
eventSenderCommunity, err := base.GetEventSender().GetCommunityByID(community.ID())
s.Require().NoError(err)
s.Require().False(eventSenderCommunity.HasMember(&user.identity.PublicKey))
declineRequestToJoin := &requests.DeclineRequestToJoinCommunity{ID: sentRequest.ID}
response, err = base.GetEventSender().DeclineRequestToJoinCommunity(declineRequestToJoin)
s.Require().NoError(err)
s.Require().NotNil(response)
// at this point, the request to join is in decline/pending state
declinedPendingRequests, err := base.GetEventSender().DeclinedPendingRequestsToJoinForCommunity(community.ID())
s.Require().NoError(err)
s.Require().Len(declinedPendingRequests, 1)
s.Require().Equal(declinedPendingRequests[0].PublicKey, common.PubkeyToHex(&user.identity.PublicKey))
// user won't receive anything
_, err = WaitOnMessengerResponse(
user,
func(r *MessengerResponse) bool { return len(r.Communities()) == 0 },
"user did not receive community request to join response",
)
s.Require().NoError(err)
}
func testRejectMemberRequestToJoin(base CommunityEventsTestsInterface, community *communities.Community, user *Messenger) {
@ -838,6 +1085,8 @@ func testRejectMemberRequestToJoin(base CommunityEventsTestsInterface, community
s.Require().NotNil(response)
s.Require().Len(response.RequestsToJoinCommunity, 1)
sentRequest := response.RequestsToJoinCommunity[0]
// event sender receives request to join
response, err = WaitOnMessengerResponse(
base.GetEventSender(),
@ -856,15 +1105,13 @@ func testRejectMemberRequestToJoin(base CommunityEventsTestsInterface, community
s.Require().NoError(err)
s.Require().Len(response.RequestsToJoinCommunity, 1)
receivedRequest := response.RequestsToJoinCommunity[0]
// event sender has not accepted request yet
eventSenderCommunity, err := base.GetEventSender().GetCommunityByID(community.ID())
s.Require().NoError(err)
s.Require().False(eventSenderCommunity.HasMember(&user.identity.PublicKey))
// event sender rejects request to join
rejectRequestToJoin := &requests.DeclineRequestToJoinCommunity{ID: receivedRequest.ID}
rejectRequestToJoin := &requests.DeclineRequestToJoinCommunity{ID: sentRequest.ID}
_, err = base.GetEventSender().DeclineRequestToJoinCommunity(rejectRequestToJoin)
s.Require().NoError(err)
@ -886,6 +1133,168 @@ func testRejectMemberRequestToJoin(base CommunityEventsTestsInterface, community
s.Require().NoError(err)
}
func testEventSenderCannotOverrideRequestToJoinState(base CommunityEventsTestsInterface, community *communities.Community, user *Messenger, additionalEventSender *Messenger) {
_, err := user.Start()
s := base.GetSuite()
s.Require().NoError(err)
defer user.Shutdown() // nolint: errcheck
advertiseCommunityTo(s, community, base.GetControlNode(), user)
// user sends request to join
requestToJoin := &requests.RequestToJoinCommunity{CommunityID: community.ID()}
response, err := user.RequestToJoinCommunity(requestToJoin)
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.RequestsToJoinCommunity, 1)
sentRequest := response.RequestsToJoinCommunity[0]
// event sender receives request to join
_, err = WaitOnMessengerResponse(
base.GetEventSender(),
func(r *MessengerResponse) bool { return len(r.RequestsToJoinCommunity) > 0 },
"event sender did not receive community request to join",
)
s.Require().NoError(err)
// event sender 2 receives request to join
_, err = WaitOnMessengerResponse(
additionalEventSender,
func(r *MessengerResponse) bool { return len(r.RequestsToJoinCommunity) > 0 },
"event sender did not receive community request to join",
)
s.Require().NoError(err)
s.Require().Len(response.RequestsToJoinCommunity, 1)
// request is pending for event sener 2
pendingRequests, err := additionalEventSender.PendingRequestsToJoinForCommunity(community.ID())
s.Require().NoError(err)
s.Require().NotNil(pendingRequests)
s.Require().Len(pendingRequests, 1)
// event sender 1 rejects request to join
rejectRequestToJoin := &requests.DeclineRequestToJoinCommunity{ID: sentRequest.ID}
_, err = base.GetEventSender().DeclineRequestToJoinCommunity(rejectRequestToJoin)
s.Require().NoError(err)
// request to join is now marked as rejected pending for event sender 1
rejectedPendingRequests, err := base.GetEventSender().DeclinedPendingRequestsToJoinForCommunity(community.ID())
s.Require().NoError(err)
s.Require().NotNil(rejectedPendingRequests)
s.Require().Len(rejectedPendingRequests, 1)
// event sender 2 receives event sender 1's decision
_, err = WaitOnMessengerResponse(
additionalEventSender,
func(r *MessengerResponse) bool { return len(r.Communities()) > 0 },
"event sender did not receive community request to join",
)
s.Require().NoError(err)
// request to join is now marked as rejected pending for event sender 2
rejectedPendingRequests, err = additionalEventSender.DeclinedPendingRequestsToJoinForCommunity(community.ID())
s.Require().NoError(err)
s.Require().NotNil(rejectedPendingRequests)
s.Require().Len(rejectedPendingRequests, 1)
// event sender 2 should not be able to override that pending state
acceptRequestToJoin := &requests.AcceptRequestToJoinCommunity{ID: sentRequest.ID}
_, err = additionalEventSender.AcceptRequestToJoinCommunity(acceptRequestToJoin)
s.Require().Error(err)
}
func testControlNodeHandlesMultipleEventSenderRequestToJoinDecisions(base CommunityEventsTestsInterface, community *communities.Community, user *Messenger, additionalEventSender *Messenger) {
_, err := user.Start()
s := base.GetSuite()
s.Require().NoError(err)
defer user.Shutdown() // nolint: errcheck
advertiseCommunityTo(s, community, base.GetControlNode(), user)
// user sends request to join
requestToJoin := &requests.RequestToJoinCommunity{CommunityID: community.ID()}
response, err := user.RequestToJoinCommunity(requestToJoin)
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.RequestsToJoinCommunity, 1)
sentRequest := response.RequestsToJoinCommunity[0]
// event sender receives request to join
_, err = WaitOnMessengerResponse(
base.GetEventSender(),
func(r *MessengerResponse) bool { return len(r.RequestsToJoinCommunity) > 0 },
"event sender did not receive community request to join",
)
s.Require().NoError(err)
// event sender 2 receives request to join
_, err = WaitOnMessengerResponse(
additionalEventSender,
func(r *MessengerResponse) bool { return len(r.RequestsToJoinCommunity) > 0 },
"event sender did not receive community request to join",
)
s.Require().NoError(err)
// control node receives request to join
_, err = WaitOnMessengerResponse(
base.GetControlNode(),
func(r *MessengerResponse) bool { return len(r.RequestsToJoinCommunity) > 0 },
"event sender did not receive community request to join",
)
s.Require().NoError(err)
// event sender 1 rejects request to join
rejectRequestToJoin := &requests.DeclineRequestToJoinCommunity{ID: sentRequest.ID}
_, err = base.GetEventSender().DeclineRequestToJoinCommunity(rejectRequestToJoin)
s.Require().NoError(err)
// request to join is now marked as rejected pending for event sender 1
rejectedPendingRequests, err := base.GetEventSender().DeclinedPendingRequestsToJoinForCommunity(community.ID())
s.Require().NoError(err)
s.Require().NotNil(rejectedPendingRequests)
s.Require().Len(rejectedPendingRequests, 1)
// control node receives event sender 1's and 2's decision
_, err = WaitOnMessengerResponse(
base.GetControlNode(),
func(r *MessengerResponse) bool { return len(r.Communities()) > 0 },
"control node did not receive event senders decision",
)
s.Require().NoError(err)
// request to join is now marked as rejected
rejectedRequests, err := base.GetControlNode().DeclinedRequestsToJoinForCommunity(community.ID())
s.Require().NoError(err)
s.Require().NotNil(rejectedRequests)
s.Require().Len(rejectedRequests, 1)
// event sender 2 accepts request to join
acceptRequestToJoin := &requests.AcceptRequestToJoinCommunity{ID: sentRequest.ID}
_, err = additionalEventSender.AcceptRequestToJoinCommunity(acceptRequestToJoin)
s.Require().NoError(err)
// request to join is now marked as accepted pending for event sender 2
acceptedPendingRequests, err := additionalEventSender.AcceptedPendingRequestsToJoinForCommunity(community.ID())
s.Require().NoError(err)
s.Require().NotNil(acceptedPendingRequests)
s.Require().Len(acceptedPendingRequests, 1)
// control node now receives event sender 2's decision
_, err = WaitOnMessengerResponse(
base.GetControlNode(),
func(r *MessengerResponse) bool { return len(r.Communities()) > 0 },
"control node did not receive event senders decision",
)
s.Require().NoError(err)
rejectedRequests, err = base.GetControlNode().DeclinedRequestsToJoinForCommunity(community.ID())
s.Require().NoError(err)
s.Require().NotNil(rejectedRequests)
s.Require().Len(rejectedRequests, 1)
// we expect user's request to join still to be rejected
s.Require().Equal(rejectedRequests[0].PublicKey, common.PubkeyToHex(&user.identity.PublicKey))
}
func testCreateEditDeleteCategories(base CommunityEventsTestsInterface, community *communities.Community) {
newCategory := &requests.CreateCommunityCategory{
CommunityID: community.ID(),

View File

@ -128,22 +128,71 @@ func (s *AdminCommunityEventsSuite) TestAdminCannotDeleteBecomeAdminPermission()
testEventSenderCannotDeleteBecomeAdminPermission(s, community)
}
func (s *AdminCommunityEventsSuite) TestAdminAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders() {
additionalAdmin := s.newMessenger()
community := setUpOnRequestCommunityAndRoles(s, protobuf.CommunityMember_ROLE_ADMIN, []*Messenger{additionalAdmin})
// set up additional user that will send request to join
user := s.newMessenger()
testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders(s, community, user, additionalAdmin)
}
func (s *AdminCommunityEventsSuite) TestAdminAcceptMemberRequestToJoinNotConfirmedByControlNode() {
community := setUpOnRequestCommunityAndRoles(s, protobuf.CommunityMember_ROLE_ADMIN, []*Messenger{})
// set up additional user that will send request to join
user := s.newMessenger()
testAcceptMemberRequestToJoinNotConfirmedByControlNode(s, community, user)
}
func (s *AdminCommunityEventsSuite) TestAdminAcceptMemberRequestToJoin() {
community := setUpOnRequestCommunityAndRoles(s, protobuf.CommunityMember_ROLE_ADMIN)
community := setUpOnRequestCommunityAndRoles(s, protobuf.CommunityMember_ROLE_ADMIN, []*Messenger{})
// set up additional user that will send request to join
user := s.newMessenger()
testAcceptMemberRequestToJoin(s, community, user)
}
func (s *AdminCommunityEventsSuite) TestAdminRejectMemberRequestToJoinResponseSharedWithOtherEventSenders() {
additionalAdmin := s.newMessenger()
community := setUpOnRequestCommunityAndRoles(s, protobuf.CommunityMember_ROLE_ADMIN, []*Messenger{additionalAdmin})
// set up additional user that will send request to join
user := s.newMessenger()
testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders(s, community, user, additionalAdmin)
}
func (s *AdminCommunityEventsSuite) TestAdminRejectMemberRequestToJoinNotConfirmedByControlNode() {
community := setUpOnRequestCommunityAndRoles(s, protobuf.CommunityMember_ROLE_ADMIN, []*Messenger{})
// set up additional user that will send request to join
user := s.newMessenger()
testRejectMemberRequestToJoinNotConfirmedByControlNode(s, community, user)
}
func (s *AdminCommunityEventsSuite) TestAdminRejectMemberRequestToJoin() {
community := setUpOnRequestCommunityAndRoles(s, protobuf.CommunityMember_ROLE_ADMIN)
community := setUpOnRequestCommunityAndRoles(s, protobuf.CommunityMember_ROLE_ADMIN, []*Messenger{})
// set up additional user that will send request to join
user := s.newMessenger()
testRejectMemberRequestToJoin(s, community, user)
}
func (s *AdminCommunityEventsSuite) TestAdminRequestToJoinStateCannotBeOverridden() {
additionalAdmin := s.newMessenger()
community := setUpOnRequestCommunityAndRoles(s, protobuf.CommunityMember_ROLE_ADMIN, []*Messenger{additionalAdmin})
// set up additional user that will send request to join
user := s.newMessenger()
testEventSenderCannotOverrideRequestToJoinState(s, community, user, additionalAdmin)
}
func (s *AdminCommunityEventsSuite) TestAdminControlNodeHandlesMultipleEventSenderRequestToJoinDecisions() {
additionalAdmin := s.newMessenger()
community := setUpOnRequestCommunityAndRoles(s, protobuf.CommunityMember_ROLE_ADMIN, []*Messenger{additionalAdmin})
// set up additional user that will send request to join
user := s.newMessenger()
testControlNodeHandlesMultipleEventSenderRequestToJoinDecisions(s, community, user, additionalAdmin)
}
func (s *AdminCommunityEventsSuite) TestAdminCreateEditDeleteCategories() {
community := setUpCommunityAndRoles(s, protobuf.CommunityMember_ROLE_ADMIN)
testCreateEditDeleteCategories(s, community)

View File

@ -254,6 +254,33 @@ func (m *Messenger) handleCommunitiesSubscription(c chan *communities.Subscripti
}
}
if sub.AcceptedRequestsToJoin != nil {
for _, requestID := range sub.AcceptedRequestsToJoin {
accept := &requests.AcceptRequestToJoinCommunity{
ID: requestID,
}
_, err := m.AcceptRequestToJoinCommunity(accept)
if err != nil {
m.logger.Warn("failed to accept request to join ", zap.Error(err))
}
// TODO INFORM ADMINS
}
}
if sub.RejectedRequestsToJoin != nil {
for _, requestID := range sub.RejectedRequestsToJoin {
reject := &requests.DeclineRequestToJoinCommunity{
ID: requestID,
}
_, err := m.DeclineRequestToJoinCommunity(reject)
if err != nil {
m.logger.Warn("failed to decline request to join ", zap.Error(err))
}
// TODO INFORM ADMINS
}
}
case <-ticker.C:
// If we are not online, we don't even try
if !m.online() {
@ -951,7 +978,15 @@ func (m *Messenger) RequestToJoinCommunity(request *requests.RequestToJoinCommun
}
if !community.AcceptRequestToJoinAutomatically() {
// send request to join also to community privileged members
// send request to join also to community admins but without revealed addresses
requestToJoinProto.RevealedAccounts = make([]*protobuf.RevealedAccount, 0)
payload, err = proto.Marshal(requestToJoinProto)
if err != nil {
return nil, err
}
rawMessage.Payload = payload
privilegedMembers := community.GetPrivilegedMembers()
for _, privilegedMember := range privilegedMembers {
_, err := m.sender.SendPrivate(context.Background(), privilegedMember, &rawMessage)
@ -1320,6 +1355,9 @@ func (m *Messenger) AcceptRequestToJoinCommunity(request *requests.AcceptRequest
if notification != nil {
notification.MembershipStatus = ActivityCenterMembershipStatusAccepted
if community.HasPermissionToSendCommunityEvents() {
notification.MembershipStatus = ActivityCenterMembershipStatusAcceptedPending
}
notification.Read = true
notification.Accepted = true
notification.UpdatedAt = m.getCurrentTimeInMillis()
@ -1353,7 +1391,19 @@ func (m *Messenger) DeclineRequestToJoinCommunity(request *requests.DeclineReque
response := &MessengerResponse{}
if notification != nil {
dbRequest, err := m.communitiesManager.GetRequestToJoin(request.ID)
if err != nil {
return nil, err
}
community, err := m.communitiesManager.GetByID(dbRequest.CommunityID)
if err != nil {
return nil, err
}
notification.MembershipStatus = ActivityCenterMembershipStatusDeclined
if community.HasPermissionToSendCommunityEvents() {
notification.MembershipStatus = ActivityCenterMembershipStatusDeclinedPending
}
notification.Read = true
notification.Dismissed = true
notification.UpdatedAt = m.getCurrentTimeInMillis()
@ -1972,6 +2022,14 @@ func (m *Messenger) AcceptedRequestsToJoinForCommunity(id types.HexBytes) ([]*co
return m.communitiesManager.AcceptedRequestsToJoinForCommunity(id)
}
func (m *Messenger) AcceptedPendingRequestsToJoinForCommunity(id types.HexBytes) ([]*communities.RequestToJoin, error) {
return m.communitiesManager.AcceptedPendingRequestsToJoinForCommunity(id)
}
func (m *Messenger) DeclinedPendingRequestsToJoinForCommunity(id types.HexBytes) ([]*communities.RequestToJoin, error) {
return m.communitiesManager.DeclinedPendingRequestsToJoinForCommunity(id)
}
func (m *Messenger) RemoveUserFromCommunity(id types.HexBytes, pkString string) (*MessengerResponse, error) {
publicKey, err := common.HexToPubkey(pkString)
if err != nil {

View File

@ -1384,13 +1384,15 @@ func (m *Messenger) HandleCommunityRequestToJoin(state *ReceivedMessageState, si
return err
}
if requestToJoin.State == communities.RequestToJoinStateAccepted {
if requestToJoin.State == communities.RequestToJoinStateAccepted || requestToJoin.State == communities.RequestToJoinStateAcceptedPending {
accept := &requests.AcceptRequestToJoinCommunity{
ID: requestToJoin.ID,
}
_, err = m.AcceptRequestToJoinCommunity(accept)
if err != nil {
if err == communities.ErrNoPermissionToJoin {
// only control node will end up here as it's the only one that
// performed token permission checks
requestToJoin.State = communities.RequestToJoinStateDeclined
} else {
return err
@ -1398,7 +1400,9 @@ func (m *Messenger) HandleCommunityRequestToJoin(state *ReceivedMessageState, si
}
}
if requestToJoin.State == communities.RequestToJoinStateDeclined {
declinedOrDeclinedPending := requestToJoin.State == communities.RequestToJoinStateDeclined || requestToJoin.State == communities.RequestToJoinStateDeclinedPending
if declinedOrDeclinedPending {
cancel := &requests.DeclineRequestToJoinCommunity{
ID: requestToJoin.ID,
}