feat: allow for pending community request state changes

iterates: status-im/status-desktop#11842
This commit is contained in:
Patryk Osmaczko 2023-10-04 21:02:17 +02:00 committed by osmaczko
parent a17ee052fb
commit bb7273cf6f
8 changed files with 150 additions and 249 deletions

View File

@ -2230,12 +2230,12 @@ func (o *Community) addMemberWithRevealedAccounts(memberKey string, roles []prot
return changes return changes
} }
func (o *Community) DeclineRequestToJoin(dbRequest *RequestToJoin) error { func (o *Community) DeclineRequestToJoin(dbRequest *RequestToJoin) (adminEventCreated bool, err error) {
o.mutex.Lock() o.mutex.Lock()
defer o.mutex.Unlock() defer o.mutex.Unlock()
if !(o.IsControlNode() || o.hasPermissionToSendCommunityEvent(protobuf.CommunityEvent_COMMUNITY_REQUEST_TO_JOIN_REJECT)) { if !(o.IsControlNode() || o.hasPermissionToSendCommunityEvent(protobuf.CommunityEvent_COMMUNITY_REQUEST_TO_JOIN_REJECT)) {
return ErrNotAuthorized return adminEventCreated, ErrNotAuthorized
} }
if o.IsControlNode() { if o.IsControlNode() {
@ -2252,13 +2252,15 @@ func (o *Community) DeclineRequestToJoin(dbRequest *RequestToJoin) error {
CommunityChanges: o.emptyCommunityChanges(), CommunityChanges: o.emptyCommunityChanges(),
RejectedRequestsToJoin: rejectedRequestsToJoin, RejectedRequestsToJoin: rejectedRequestsToJoin,
} }
err := o.addNewCommunityEvent(o.ToCommunityRequestToJoinRejectCommunityEvent(adminChanges)) err = o.addNewCommunityEvent(o.ToCommunityRequestToJoinRejectCommunityEvent(adminChanges))
if err != nil { if err != nil {
return err return adminEventCreated, err
} }
adminEventCreated = true
} }
return nil return adminEventCreated, err
} }
func (o *Community) ValidateEvent(event *CommunityEvent, signer *ecdsa.PublicKey) error { func (o *Community) ValidateEvent(event *CommunityEvent, signer *ecdsa.PublicKey) error {

View File

@ -1424,7 +1424,7 @@ func (m *Manager) handleCommunityDescriptionMessageCommon(community *Community,
// We mark our requests as completed, though maybe we should mark // We mark our requests as completed, though maybe we should mark
// any request for any user that has been added as completed // any request for any user that has been added as completed
if err := m.markRequestToJoin(&m.identity.PublicKey, community); err != nil { if err := m.markRequestToJoinAsAccepted(&m.identity.PublicKey, community); err != nil {
return nil, err return nil, err
} }
// Check if there's a change and we should be joining // Check if there's a change and we should be joining
@ -1702,18 +1702,11 @@ func (m *Manager) handleCommunityEventRequestAccepted(community *Community, comm
return nil, err return nil, err
} }
if community.IsControlNode() { if existingRequestToJoin != nil {
// If request to join exists in control node, save request as RequestToJoinStateAccepted alreadyProcessedByControlNode := existingRequestToJoin.State == RequestToJoinStateAccepted || existingRequestToJoin.State == RequestToJoinStateDeclined
// If request to join does not exist in control node, save request as RequestToJoinStateAcceptedPending if alreadyProcessedByControlNode || existingRequestToJoin.State == RequestToJoinStateCanceled {
// as privileged users don't have revealed addresses. This can happen if control node received continue
// community event message before user request to join
if existingRequestToJoin != nil {
requestToJoin.State = RequestToJoinStateAccepted
} }
} else if community.HasPermissionToSendCommunityEvents() && existingRequestToJoin.State != RequestToJoinStatePending {
// the request is already in some pending state or was processed by a control node,
// so we won't override it again
continue
} }
requestUpdated, err := m.saveOrUpdateRequestToJoin(community.ID(), requestToJoin) requestUpdated, err := m.saveOrUpdateRequestToJoin(community.ID(), requestToJoin)
@ -1721,11 +1714,11 @@ func (m *Manager) handleCommunityEventRequestAccepted(community *Community, comm
return nil, err return nil, err
} }
// If request to join exists in control node, add request to acceptedRequestsToJoin.
// Otherwise keep the request as RequestToJoinStateAcceptedPending,
// as privileged users don't have revealed addresses. This can happen if control node received
// community event message before user request to join.
if community.IsControlNode() && requestUpdated { 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) acceptedRequestsToJoin = append(acceptedRequestsToJoin, requestToJoin.ID)
} }
@ -1757,24 +1750,21 @@ func (m *Manager) handleCommunityEventRequestRejected(community *Community, comm
return nil, err return nil, err
} }
if community.IsControlNode() { if existingRequestToJoin != nil {
// If request to join exists in control node, save request as RequestToJoinStateDeclined alreadyProcessedByControlNode := existingRequestToJoin.State == RequestToJoinStateAccepted || existingRequestToJoin.State == RequestToJoinStateDeclined
// If request to join does not exist in control node, save request as RequestToJoinStateDeclinedPending if alreadyProcessedByControlNode || existingRequestToJoin.State == RequestToJoinStateCanceled {
// as privileged users don't have revealed addresses. This can happen if control node received continue
// community event message before user request to join
if existingRequestToJoin != nil {
requestToJoin.State = RequestToJoinStateDeclined
} }
} else if community.HasPermissionToSendCommunityEvents() && existingRequestToJoin.State != RequestToJoinStatePending {
// the request is already in some pending state or was processed by a control node,
// so we won't override it again
continue
} }
requestUpdated, err := m.saveOrUpdateRequestToJoin(community.ID(), requestToJoin) requestUpdated, err := m.saveOrUpdateRequestToJoin(community.ID(), requestToJoin)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// If request to join exists in control node, add request to rejectedRequestsToJoin.
// Otherwise keep the request as RequestToJoinStateDeclinedPending,
// as privileged users don't have revealed addresses. This can happen if control node received
// community event message before user request to join.
if community.IsControlNode() && requestUpdated { if community.IsControlNode() && requestUpdated {
rejectedRequestsToJoin = append(rejectedRequestsToJoin, requestToJoin.ID) rejectedRequestsToJoin = append(rejectedRequestsToJoin, requestToJoin.ID)
} }
@ -1788,9 +1778,9 @@ func (m *Manager) handleCommunityEventRequestRejected(community *Community, comm
return requestsToJoin, nil return requestsToJoin, nil
} }
// markRequestToJoin marks all the pending requests to join as completed // markRequestToJoinAsAccepted marks all the pending requests to join as completed
// if we are members // if we are members
func (m *Manager) markRequestToJoin(pk *ecdsa.PublicKey, community *Community) error { func (m *Manager) markRequestToJoinAsAccepted(pk *ecdsa.PublicKey, community *Community) error {
if community.HasMember(pk) { if community.HasMember(pk) {
return m.persistence.SetRequestToJoinState(common.PubkeyToHex(pk), community.ID(), RequestToJoinStateAccepted) return m.persistence.SetRequestToJoinState(common.PubkeyToHex(pk), community.ID(), RequestToJoinStateAccepted)
} }
@ -1956,34 +1946,6 @@ func (m *Manager) AcceptRequestToJoin(dbRequest *RequestToJoin) (*Community, err
return nil, err return nil, err
} }
if community.hasPermissionToSendCommunityEvent(protobuf.CommunityEvent_COMMUNITY_REQUEST_TO_JOIN_ACCEPT) && !community.IsControlNode() {
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")
}
// 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 community.IsControlNode() { if community.IsControlNode() {
revealedAccounts, err := m.persistence.GetRequestToJoinRevealedAddresses(dbRequest.ID) revealedAccounts, err := m.persistence.GetRequestToJoinRevealedAddresses(dbRequest.ID)
if err != nil { if err != nil {
@ -2021,17 +1983,10 @@ func (m *Manager) AcceptRequestToJoin(dbRequest *RequestToJoin) (*Community, err
} }
} }
if err := m.markRequestToJoin(pk, community); err != nil { if err := m.markRequestToJoinAsAccepted(pk, community); err != nil {
return nil, err return nil, err
} }
}
err = m.saveAndPublish(community)
if err != nil {
return nil, err
}
if community.IsControlNode() {
if err = m.shareAcceptedRequestToJoinWithPrivilegedMembers(community, dbRequest); err != nil { if err = m.shareAcceptedRequestToJoinWithPrivilegedMembers(community, dbRequest); err != nil {
return nil, err return nil, err
} }
@ -2047,6 +2002,31 @@ func (m *Manager) AcceptRequestToJoin(dbRequest *RequestToJoin) (*Community, err
return nil, err return nil, err
} }
} }
} else if community.hasPermissionToSendCommunityEvent(protobuf.CommunityEvent_COMMUNITY_REQUEST_TO_JOIN_ACCEPT) {
// 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
}
} else {
return nil, ErrNotAuthorized
}
err = m.saveAndPublish(community)
if err != nil {
return nil, err
} }
return community, nil return community, nil
@ -2062,16 +2042,17 @@ func (m *Manager) DeclineRequestToJoin(dbRequest *RequestToJoin) (*Community, er
return nil, err return nil, err
} }
requestToJoinState := RequestToJoinStateDeclined adminEventCreated, err := community.DeclineRequestToJoin(dbRequest)
if community.HasPermissionToSendCommunityEvents() {
requestToJoinState = RequestToJoinStateDeclinedPending
}
err = m.persistence.SetRequestToJoinState(dbRequest.PublicKey, dbRequest.CommunityID, requestToJoinState)
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = community.DeclineRequestToJoin(dbRequest) requestToJoinState := RequestToJoinStateDeclined
if adminEventCreated {
requestToJoinState = RequestToJoinStateDeclinedPending // can only be declined by control node
}
err = m.persistence.SetRequestToJoinState(dbRequest.PublicKey, dbRequest.CommunityID, requestToJoinState)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -2146,7 +2127,7 @@ func (m *Manager) HandleCommunityRequestToJoin(signer *ecdsa.PublicKey, receiver
// don't process request as admin if community is configured as auto-accept // don't process request as admin if community is configured as auto-accept
if !community.IsControlNode() && community.AcceptRequestToJoinAutomatically() { if !community.IsControlNode() && community.AcceptRequestToJoinAutomatically() {
return nil, errors.New("ignoring request to join, community is set to auto-accept") return nil, nil
} }
// control node must receive requests to join only on community address // control node must receive requests to join only on community address
@ -2188,93 +2169,69 @@ func (m *Manager) HandleCommunityRequestToJoin(signer *ecdsa.PublicKey, receiver
return nil, err return nil, err
} }
if existingRequestToJoin != nil && existingRequestToJoin.State != RequestToJoinStateCanceled { if existingRequestToJoin == nil || existingRequestToJoin.State == RequestToJoinStateCanceled {
if err := m.persistence.SaveRequestToJoin(requestToJoin); err != nil {
return nil, err
}
}
if community.IsControlNode() {
// request to join was already processed by an admin and waits to get // request to join was already processed by an admin and waits to get
// confirmation for its decision // confirmation for its decision
// //
// we're only interested in immediately declining any declined/pending // we're only interested in immediately declining any declined/pending
// requests here, because if it's accepted/pending, we still need to perform // requests here, because if it's accepted/pending, we still need to perform
// some checks // some checks
if existingRequestToJoin.State == RequestToJoinStateDeclinedPending { if existingRequestToJoin != nil && existingRequestToJoin.State == RequestToJoinStateDeclinedPending {
if community.IsControlNode() { requestToJoin.State = RequestToJoinStateDeclined
requestToJoin.State = RequestToJoinStateDeclined
}
return requestToJoin, nil return requestToJoin, nil
} }
} else {
if err := m.persistence.SaveRequestToJoin(requestToJoin); err != nil {
return nil, err
}
}
if len(request.RevealedAccounts) > 0 && community.IsControlNode() { if len(request.RevealedAccounts) > 0 {
// verify if revealed addresses indeed belong to requester // verify if revealed addresses indeed belong to requester
for _, revealedAccount := range request.RevealedAccounts { for _, revealedAccount := range request.RevealedAccounts {
recoverParams := account.RecoverParams{ recoverParams := account.RecoverParams{
Message: types.EncodeHex(crypto.Keccak256(crypto.CompressPubkey(signer), community.ID(), requestToJoin.ID)), Message: types.EncodeHex(crypto.Keccak256(crypto.CompressPubkey(signer), community.ID(), requestToJoin.ID)),
Signature: types.EncodeHex(revealedAccount.Signature), Signature: types.EncodeHex(revealedAccount.Signature),
}
matching, err := m.accountsManager.CanRecover(recoverParams, types.HexToAddress(revealedAccount.Address))
if err != nil {
return nil, err
}
if !matching {
// if ownership of only one wallet address cannot be verified,
// we mark the request as cancelled and stop
requestToJoin.State = RequestToJoinStateDeclined
return requestToJoin, nil
}
} }
matching, err := m.accountsManager.CanRecover(recoverParams, types.HexToAddress(revealedAccount.Address)) // Save revealed addresses + signatures so they can later be added
// to the control node's local table of known revealed addresses
err = m.persistence.SaveRequestToJoinRevealedAddresses(requestToJoin.ID, requestToJoin.RevealedAccounts)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !matching {
// if ownership of only one wallet address cannot be verified,
// we mark the request as cancelled and stop
requestToJoin.State = RequestToJoinStateDeclined
return requestToJoin, nil
}
} }
// Save revealed addresses + signatures so they can later be added // If user is already a member, then accept request automatically
// to the control node's local table of known revealed addresses // It may happen when member removes itself from community and then tries to rejoin
err = m.persistence.SaveRequestToJoinRevealedAddresses(requestToJoin.ID, requestToJoin.RevealedAccounts) // More specifically, CommunityRequestToLeave may be delivered later than CommunityRequestToJoin, or not delivered at all
if err != nil { acceptAutomatically := community.AcceptRequestToJoinAutomatically() || community.HasMember(signer)
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
acceptedByAdmin := existingRequestToJoin != nil && existingRequestToJoin.State == RequestToJoinStateAcceptedPending
// If user is already a member, then accept request automatically if acceptAutomatically || acceptedByAdmin {
// It may happen when member removes itself from community and then tries to rejoin err = m.markRequestToJoinAsAccepted(signer, community)
// More specifically, CommunityRequestToLeave may be delivered later than CommunityRequestToJoin, or not delivered at all
acceptAutomatically := community.AcceptRequestToJoinAutomatically() || community.HasMember(signer)
if acceptAutomatically {
if community.IsControlNode() {
err = m.markRequestToJoin(signer, community)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Don't check permissions here, // Don't check permissions here,
// it will be done further in the processing pipeline. // it will be done further in the processing pipeline.
requestToJoin.State = RequestToJoinStateAccepted requestToJoin.State = RequestToJoinStateAccepted
} else { return requestToJoin, nil
err = m.markRequestToJoinAsAcceptedPending(signer, community)
if err != nil {
return nil, err
}
requestToJoin.State = RequestToJoinStateAcceptedPending
}
return requestToJoin, nil
}
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 != nil && 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
} }
} }
@ -2882,7 +2839,7 @@ func (m *Manager) HandleCommunityRequestToJoinResponse(signer *ecdsa.PublicKey,
} }
if request.Accepted { if request.Accepted {
err = m.markRequestToJoin(&m.identity.PublicKey, community) err = m.markRequestToJoinAsAccepted(&m.identity.PublicKey, community)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -74,10 +74,6 @@ 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 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) { func AddTimeoutToRequestToJoinClock(clock uint64) (uint64, error) {
requestToJoinClock, err := strconv.ParseInt(fmt.Sprint(clock), 10, 64) requestToJoinClock, err := strconv.ParseInt(fmt.Sprint(clock), 10, 64)
if err != nil { if err != nil {

View File

@ -147,15 +147,6 @@ func (s *OwnerWithoutCommunityKeyCommunityEventsSuite) TestOwnerRejectMemberRequ
testRejectMemberRequestToJoin(s, community, user) testRejectMemberRequestToJoin(s, community, user)
} }
func (s *OwnerWithoutCommunityKeyCommunityEventsSuite) TestOwnerRequestToJoinStateCannotBeOverridden() {
additionalOwner := s.newMessenger("", []string{})
community := setUpOnRequestCommunityAndRoles(s, protobuf.CommunityMember_ROLE_OWNER, []*Messenger{additionalOwner})
// set up additional user that will send request to join
user := s.newMessenger("", []string{})
testEventSenderCannotOverrideRequestToJoinState(s, community, user, additionalOwner)
}
func (s *OwnerWithoutCommunityKeyCommunityEventsSuite) TestOwnerControlNodeHandlesMultipleEventSenderRequestToJoinDecisions() { func (s *OwnerWithoutCommunityKeyCommunityEventsSuite) TestOwnerControlNodeHandlesMultipleEventSenderRequestToJoinDecisions() {
additionalOwner := s.newMessenger("", []string{}) additionalOwner := s.newMessenger("", []string{})
community := setUpOnRequestCommunityAndRoles(s, protobuf.CommunityMember_ROLE_OWNER, []*Messenger{additionalOwner}) community := setUpOnRequestCommunityAndRoles(s, protobuf.CommunityMember_ROLE_OWNER, []*Messenger{additionalOwner})

View File

@ -168,15 +168,6 @@ func (s *TokenMasterCommunityEventsSuite) TestTokenMasterRejectMemberRequestToJo
testRejectMemberRequestToJoin(s, community, user) testRejectMemberRequestToJoin(s, community, user)
} }
func (s *TokenMasterCommunityEventsSuite) TestTokenMasterRequestToJoinStateCannotBeOverridden() {
additionalTokenMaster := s.newMessenger("", []string{})
community := setUpOnRequestCommunityAndRoles(s, protobuf.CommunityMember_ROLE_TOKEN_MASTER, []*Messenger{additionalTokenMaster})
// set up additional user that will send request to join
user := s.newMessenger("", []string{})
testEventSenderCannotOverrideRequestToJoinState(s, community, user, additionalTokenMaster)
}
func (s *TokenMasterCommunityEventsSuite) TestTokenMasterControlNodeHandlesMultipleEventSenderRequestToJoinDecisions() { func (s *TokenMasterCommunityEventsSuite) TestTokenMasterControlNodeHandlesMultipleEventSenderRequestToJoinDecisions() {
additionalTokenMaster := s.newMessenger("qwerty", []string{eventsSenderAccountAddress}) additionalTokenMaster := s.newMessenger("qwerty", []string{eventsSenderAccountAddress})
community := setUpOnRequestCommunityAndRoles(s, protobuf.CommunityMember_ROLE_TOKEN_MASTER, []*Messenger{additionalTokenMaster}) community := setUpOnRequestCommunityAndRoles(s, protobuf.CommunityMember_ROLE_TOKEN_MASTER, []*Messenger{additionalTokenMaster})

View File

@ -1211,6 +1211,26 @@ func testAcceptMemberRequestToJoinResponseSharedWithOtherEventSenders(base Commu
s.Require().NoError(err) s.Require().NoError(err)
s.Require().Len(acceptedPendingRequests, 1) s.Require().Len(acceptedPendingRequests, 1)
s.Require().Equal(acceptedPendingRequests[0].PublicKey, common.PubkeyToHex(&user.identity.PublicKey)) s.Require().Equal(acceptedPendingRequests[0].PublicKey, common.PubkeyToHex(&user.identity.PublicKey))
// event sender 1 changes its mind and rejects the request
rejectRequestToJoin := &requests.DeclineRequestToJoinCommunity{ID: sentRequest.ID}
response, err = base.GetEventSender().DeclineRequestToJoinCommunity(rejectRequestToJoin)
s.Require().NoError(err)
s.Require().NotNil(response)
// event sender 2 receives updated 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 testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders(base CommunityEventsTestsInterface, community *communities.Community, user *Messenger, additionalEventSender *Messenger) { func testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders(base CommunityEventsTestsInterface, community *communities.Community, user *Messenger, additionalEventSender *Messenger) {
@ -1269,6 +1289,27 @@ func testRejectMemberRequestToJoinResponseSharedWithOtherEventSenders(base Commu
s.Require().NoError(err) s.Require().NoError(err)
s.Require().Len(rejectedPendingRequests, 1) s.Require().Len(rejectedPendingRequests, 1)
s.Require().Equal(rejectedPendingRequests[0].PublicKey, common.PubkeyToHex(&user.identity.PublicKey)) s.Require().Equal(rejectedPendingRequests[0].PublicKey, common.PubkeyToHex(&user.identity.PublicKey))
// event sender 1 changes its mind and accepts the 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)
// event sender 2 receives updated 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 testRejectMemberRequestToJoin(base CommunityEventsTestsInterface, community *communities.Community, user *Messenger) { func testRejectMemberRequestToJoin(base CommunityEventsTestsInterface, community *communities.Community, user *Messenger) {
@ -1367,78 +1408,6 @@ func testRejectMemberRequestToJoin(base CommunityEventsTestsInterface, community
s.Require().Len(declinedRequestsPending, 0) s.Require().Len(declinedRequestsPending, 0)
} }
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) { func testControlNodeHandlesMultipleEventSenderRequestToJoinDecisions(base CommunityEventsTestsInterface, community *communities.Community, user *Messenger, additionalEventSender *Messenger) {
_, err := user.Start() _, err := user.Start()

View File

@ -175,15 +175,6 @@ func (s *AdminCommunityEventsSuite) TestAdminRejectMemberRequestToJoin() {
testRejectMemberRequestToJoin(s, community, user) testRejectMemberRequestToJoin(s, community, user)
} }
func (s *AdminCommunityEventsSuite) TestAdminRequestToJoinStateCannotBeOverridden() {
additionalAdmin := s.newMessenger("qwerty", []string{eventsSenderAccountAddress})
community := setUpOnRequestCommunityAndRoles(s, protobuf.CommunityMember_ROLE_ADMIN, []*Messenger{additionalAdmin})
// set up additional user that will send request to join
user := s.newMessenger("", []string{})
testEventSenderCannotOverrideRequestToJoinState(s, community, user, additionalAdmin)
}
func (s *AdminCommunityEventsSuite) TestAdminControlNodeHandlesMultipleEventSenderRequestToJoinDecisions() { func (s *AdminCommunityEventsSuite) TestAdminControlNodeHandlesMultipleEventSenderRequestToJoinDecisions() {
additionalAdmin := s.newMessenger("qwerty", []string{eventsSenderAccountAddress}) additionalAdmin := s.newMessenger("qwerty", []string{eventsSenderAccountAddress})
community := setUpOnRequestCommunityAndRoles(s, protobuf.CommunityMember_ROLE_ADMIN, []*Messenger{additionalAdmin}) community := setUpOnRequestCommunityAndRoles(s, protobuf.CommunityMember_ROLE_ADMIN, []*Messenger{additionalAdmin})
@ -402,7 +393,7 @@ func (s *AdminCommunityEventsSuite) TestAdminDoesNotHaveRejectedEventsLoop() {
s.Require().NoError(err) s.Require().NoError(err)
// Update community clock without publishing new CommunityDescription // Update community clock without publishing new CommunityDescription
err = community.DeclineRequestToJoin(nil) _, err = community.DeclineRequestToJoin(nil)
s.Require().NoError(err) s.Require().NoError(err)
err = s.owner.communitiesManager.SaveCommunity(community) err = s.owner.communitiesManager.SaveCommunity(community)

View File

@ -1435,8 +1435,12 @@ func (m *Messenger) HandleCommunityRequestToJoin(state *ReceivedMessageState, re
if err != nil { if err != nil {
return err return err
} }
// not interested, stop further processing
if requestToJoin == nil {
return nil
}
if requestToJoin.State == communities.RequestToJoinStateAccepted || requestToJoin.State == communities.RequestToJoinStateAcceptedPending { if requestToJoin.State == communities.RequestToJoinStateAccepted {
accept := &requests.AcceptRequestToJoinCommunity{ accept := &requests.AcceptRequestToJoinCommunity{
ID: requestToJoin.ID, ID: requestToJoin.ID,
} }
@ -1452,7 +1456,7 @@ func (m *Messenger) HandleCommunityRequestToJoin(state *ReceivedMessageState, re
} }
} }
if requestToJoin.State == communities.RequestToJoinStateDeclined || requestToJoin.State == communities.RequestToJoinStateDeclinedPending { if requestToJoin.State == communities.RequestToJoinStateDeclined {
cancel := &requests.DeclineRequestToJoinCommunity{ cancel := &requests.DeclineRequestToJoinCommunity{
ID: requestToJoin.ID, ID: requestToJoin.ID,
} }