From 3bf0bed78da660b0a361afd285ec229dcef7de70 Mon Sep 17 00:00:00 2001 From: Jonathan Rainville Date: Fri, 18 Aug 2023 15:52:13 -0400 Subject: [PATCH] Fix pending join requests + API to get them (#3902) Needed for https://github.com/status-im/status-desktop/issues/11851 --- protocol/communities/manager.go | 158 +++++++++++++--------------- protocol/communities/persistence.go | 2 +- protocol/messenger_communities.go | 144 ++++++++++++++++++++++--- services/ext/api.go | 5 + 4 files changed, 213 insertions(+), 96 deletions(-) diff --git a/protocol/communities/manager.go b/protocol/communities/manager.go index 4333f05ac..703f219f6 100644 --- a/protocol/communities/manager.go +++ b/protocol/communities/manager.go @@ -308,8 +308,9 @@ type Subscription struct { } type CommunityResponse struct { - Community *Community `json:"community"` - Changes *CommunityChanges `json:"changes"` + Community *Community `json:"community"` + Changes *CommunityChanges `json:"changes"` + RequestsToJoin []*RequestToJoin `json:"requestsToJoin"` } type CommunityEventsMessageInvalidClockSignal struct { @@ -1422,7 +1423,7 @@ func (m *Manager) HandleCommunityEventsMessage(signer *ecdsa.PublicKey, message return nil, err } - err = m.handleAdditionalAdminChanges(changes.Community) + additionalCommunityResponse, err := m.handleAdditionalAdminChanges(changes.Community) if err != nil { return nil, err } @@ -1449,8 +1450,9 @@ func (m *Manager) HandleCommunityEventsMessage(signer *ecdsa.PublicKey, message } return &CommunityResponse{ - Community: changes.Community, - Changes: changes, + Community: changes.Community, + Changes: changes, + RequestsToJoin: additionalCommunityResponse.RequestsToJoin, }, nil } @@ -1502,107 +1504,89 @@ func (m *Manager) HandleCommunityEventsMessageRejected(signer *ecdsa.PublicKey, return reapplyEventsMessage, nil } -func (m *Manager) HandleCommunityPrivilegedUserSyncMessage(signer *ecdsa.PublicKey, message *protobuf.CommunityPrivilegedUserSyncMessage) error { - if signer == nil { - return errors.New("signer can't be nil") +func (m *Manager) HandleRequestToJoinPrivilegedUserSyncMessage(message *protobuf.CommunityPrivilegedUserSyncMessage, communityID types.HexBytes) ([]*RequestToJoin, error) { + var state RequestToJoinState + if message.Type == protobuf.CommunityPrivilegedUserSyncMessage_CONTROL_NODE_ACCEPT_REQUEST_TO_JOIN { + state = RequestToJoinStateAccepted + } else { + state = RequestToJoinStateDeclined } - community, err := m.persistence.GetByID(&m.identity.PublicKey, message.CommunityId) - if err != nil { - return err + requestsToJoin := make([]*RequestToJoin, 0) + for signer, requestToJoinProto := range message.RequestToJoin { + requestToJoin := &RequestToJoin{ + PublicKey: signer, + Clock: requestToJoinProto.Clock, + ENSName: requestToJoinProto.EnsName, + CommunityID: requestToJoinProto.CommunityId, + State: state, + } + requestToJoin.CalculateID() + + _, err := m.saveOrUpdateRequestToJoin(signer, communityID, requestToJoin) + if err != nil { + return nil, err + } + requestsToJoin = append(requestsToJoin, requestToJoin) } - if community == nil { - return ErrOrgNotFound - } + return requestsToJoin, nil +} - if !community.IsPrivilegedMember(&m.identity.PublicKey) { - return errors.New("user has no permissions to process privileged sync message") - } - - err = validateCommunityPrivilegedUserSyncMessage(message) - if err != nil { - return err - } - - switch message.Type { - case protobuf.CommunityPrivilegedUserSyncMessage_CONTROL_NODE_ACCEPT_REQUEST_TO_JOIN: - fallthrough - case protobuf.CommunityPrivilegedUserSyncMessage_CONTROL_NODE_REJECT_REQUEST_TO_JOIN: - if !common.IsPubKeyEqual(community.PublicKey(), signer) { - return errors.New("accepted/requested to join sync messages can be send only by the control node") +func (m *Manager) HandleAddCommunityTokenPrivilegedUserSyncMessage(message *protobuf.CommunityPrivilegedUserSyncMessage, community *Community) error { + for _, token := range message.CommunityTokens { + token := community_token.FromCommunityTokenProtobuf(token) + if !community.MemberCanManageToken(community.MemberIdentity(), token) { + return ErrInvalidManageTokensPermission } - var state RequestToJoinState - if message.Type == protobuf.CommunityPrivilegedUserSyncMessage_CONTROL_NODE_ACCEPT_REQUEST_TO_JOIN { - state = RequestToJoinStateAccepted - } else { - state = RequestToJoinStateDeclined + exist, err := m.persistence.HasCommunityToken(token.CommunityID, token.Address, token.ChainID) + if err != nil { + return err } - for signer, requestToJoinProto := range message.RequestToJoin { - requestToJoin := &RequestToJoin{ - PublicKey: signer, - Clock: requestToJoinProto.Clock, - ENSName: requestToJoinProto.EnsName, - CommunityID: requestToJoinProto.CommunityId, - State: state, - } - requestToJoin.CalculateID() - - _, err := m.saveOrUpdateRequestToJoin(signer, community.ID(), requestToJoin) - if err != nil { - return err - } + if !exist { + return m.persistence.AddCommunityToken(token) } - case protobuf.CommunityPrivilegedUserSyncMessage_ADD_COMMUNITY_TOKENS: - for _, token := range message.CommunityTokens { - token := community_token.FromCommunityTokenProtobuf(token) - if !community.MemberCanManageToken(community.MemberIdentity(), token) { - return ErrInvalidManageTokensPermission - } - - exist, err := m.persistence.HasCommunityToken(token.CommunityID, token.Address, token.ChainID) - if err != nil { - return err - } - - if !exist { - return m.persistence.AddCommunityToken(token) - } - } - } - return nil } -func (m *Manager) handleAdditionalAdminChanges(community *Community) error { +func (m *Manager) handleAdditionalAdminChanges(community *Community) (*CommunityResponse, error) { + communityResponse := CommunityResponse{ + RequestsToJoin: make([]*RequestToJoin, 0), + } if !(community.IsControlNode() || community.HasPermissionToSendCommunityEvents()) { // we're a normal user/member node, so there's nothing for us to do here - return nil + return &communityResponse, nil } for i := range community.config.EventsData.Events { communityEvent := &community.config.EventsData.Events[i] switch communityEvent.Type { case protobuf.CommunityEvent_COMMUNITY_REQUEST_TO_JOIN_ACCEPT: - err := m.handleCommunityEventRequestAccepted(community, communityEvent) + requestsToJoin, err := m.handleCommunityEventRequestAccepted(community, communityEvent) if err != nil { - return err + return nil, err + } + if requestsToJoin != nil { + communityResponse.RequestsToJoin = append(communityResponse.RequestsToJoin, requestsToJoin...) } case protobuf.CommunityEvent_COMMUNITY_REQUEST_TO_JOIN_REJECT: - err := m.handleCommunityEventRequestRejected(community, communityEvent) + requestsToJoin, err := m.handleCommunityEventRequestRejected(community, communityEvent) if err != nil { - return err + return nil, err + } + if requestsToJoin != nil { + communityResponse.RequestsToJoin = append(communityResponse.RequestsToJoin, requestsToJoin...) } default: } } - return nil + return &communityResponse, nil } func (m *Manager) saveOrUpdateRequestToJoin(signer string, communityID types.HexBytes, requestToJoin *RequestToJoin) (bool, error) { @@ -1636,9 +1620,11 @@ func (m *Manager) saveOrUpdateRequestToJoin(signer string, communityID types.Hex return updated, nil } -func (m *Manager) handleCommunityEventRequestAccepted(community *Community, communityEvent *CommunityEvent) error { +func (m *Manager) handleCommunityEventRequestAccepted(community *Community, communityEvent *CommunityEvent) ([]*RequestToJoin, error) { acceptedRequestsToJoin := make([]types.HexBytes, 0) + requestsToJoin := make([]*RequestToJoin, 0) + for signer, request := range communityEvent.AcceptedRequestsToJoin { requestToJoin := &RequestToJoin{ PublicKey: signer, @@ -1651,7 +1637,7 @@ func (m *Manager) handleCommunityEventRequestAccepted(community *Community, comm existingRequestToJoin, err := m.persistence.GetRequestToJoin(requestToJoin.ID) if err != nil && err != sql.ErrNoRows { - return err + return nil, err } if community.IsControlNode() { @@ -1670,7 +1656,7 @@ func (m *Manager) handleCommunityEventRequestAccepted(community *Community, comm requestUpdated, err := m.saveOrUpdateRequestToJoin(signer, community.ID(), requestToJoin) if err != nil { - return err + return nil, err } if community.IsControlNode() && requestUpdated { @@ -1680,16 +1666,20 @@ func (m *Manager) handleCommunityEventRequestAccepted(community *Community, comm // admin nodes), so we don't want to trigger an `AcceptRequestToJoin` in such cases. acceptedRequestsToJoin = append(acceptedRequestsToJoin, requestToJoin.ID) } + + requestsToJoin = append(requestsToJoin, requestToJoin) } if community.IsControlNode() { m.publish(&Subscription{AcceptedRequestsToJoin: acceptedRequestsToJoin}) } - return nil + return requestsToJoin, nil } -func (m *Manager) handleCommunityEventRequestRejected(community *Community, communityEvent *CommunityEvent) error { +func (m *Manager) handleCommunityEventRequestRejected(community *Community, communityEvent *CommunityEvent) ([]*RequestToJoin, error) { rejectedRequestsToJoin := make([]types.HexBytes, 0) + requestsToJoin := make([]*RequestToJoin, 0) + for signer, request := range communityEvent.RejectedRequestsToJoin { requestToJoin := &RequestToJoin{ PublicKey: signer, @@ -1702,7 +1692,7 @@ func (m *Manager) handleCommunityEventRequestRejected(community *Community, comm existingRequestToJoin, err := m.persistence.GetRequestToJoin(requestToJoin.ID) if err != nil && err != sql.ErrNoRows { - return err + return nil, err } if community.IsControlNode() { @@ -1721,17 +1711,19 @@ func (m *Manager) handleCommunityEventRequestRejected(community *Community, comm requestUpdated, err := m.saveOrUpdateRequestToJoin(signer, community.ID(), requestToJoin) if err != nil { - return err + return nil, err } if community.IsControlNode() && requestUpdated { rejectedRequestsToJoin = append(rejectedRequestsToJoin, requestToJoin.ID) } + + requestsToJoin = append(requestsToJoin, requestToJoin) } if community.IsControlNode() { m.publish(&Subscription{RejectedRequestsToJoin: rejectedRequestsToJoin}) } - return nil + return requestsToJoin, nil } // markRequestToJoin marks all the pending requests to join as completed @@ -2110,7 +2102,7 @@ func (m *Manager) HandleCommunityRequestToJoin(signer *ecdsa.PublicKey, request return nil, err } - if existingRequestToJoin != nil { + if existingRequestToJoin != nil && existingRequestToJoin.State != RequestToJoinStateCanceled { // request to join was already processed by an admin and waits to get // confirmation for its decision // @@ -4636,7 +4628,7 @@ func (m *Manager) HandleCommunityTokensMetadata(communityID string, communityTok return nil } -func validateCommunityPrivilegedUserSyncMessage(message *protobuf.CommunityPrivilegedUserSyncMessage) error { +func (m *Manager) ValidateCommunityPrivilegedUserSyncMessage(message *protobuf.CommunityPrivilegedUserSyncMessage) error { if message == nil { return errors.New("invalid CommunityPrivilegedUserSyncMessage message") } diff --git a/protocol/communities/persistence.go b/protocol/communities/persistence.go index c8545fb2b..eeb02a320 100644 --- a/protocol/communities/persistence.go +++ b/protocol/communities/persistence.go @@ -374,7 +374,7 @@ func (p *Persistence) SaveRequestToJoin(request *RequestToJoin) (err error) { return ErrOldRequestToJoin } - _, err = tx.Exec(`INSERT INTO communities_requests_to_join(id,public_key,clock,ens_name,chat_id,community_id,state) VALUES (?, ?, ?, ?, ?, ?, ?)`, request.ID, request.PublicKey, request.Clock, request.ENSName, request.ChatID, request.CommunityID, request.State) + _, err = tx.Exec(`INSERT OR REPLACE INTO communities_requests_to_join(id,public_key,clock,ens_name,chat_id,community_id,state) VALUES (?, ?, ?, ?, ?, ?, ?)`, request.ID, request.PublicKey, request.Clock, request.ENSName, request.ChatID, request.CommunityID, request.State) return err } diff --git a/protocol/messenger_communities.go b/protocol/messenger_communities.go index 5235d1cf9..07aa7dec0 100644 --- a/protocol/messenger_communities.go +++ b/protocol/messenger_communities.go @@ -1308,6 +1308,19 @@ func (m *Messenger) CancelRequestToJoinCommunity(request *requests.CancelRequest return nil, err } + if !community.AcceptRequestToJoinAutomatically() { + // send cancelation to community admins also + rawMessage.Payload = payload + + privilegedMembers := community.GetPrivilegedMembers() + for _, privilegedMember := range privilegedMembers { + _, err := m.sender.SendPrivate(context.Background(), privilegedMember, &rawMessage) + if err != nil { + return nil, err + } + } + } + response := &MessengerResponse{} response.AddCommunity(community) response.RequestsToJoinCommunity = append(response.RequestsToJoinCommunity, requestToJoin) @@ -1354,17 +1367,18 @@ func (m *Messenger) AcceptRequestToJoinCommunity(request *requests.AcceptRequest return nil, err } - pk, err := common.HexToPubkey(requestToJoin.PublicKey) - if err != nil { - return nil, err - } - - grant, err := community.BuildGrant(pk, "") - if err != nil { - return nil, err - } - if community.IsControlNode() { + // If we are the control node, we send the response to the user + pk, err := common.HexToPubkey(requestToJoin.PublicKey) + if err != nil { + return nil, err + } + + grant, err := community.BuildGrant(pk, "") + if err != nil { + return nil, err + } + requestToJoinResponseProto := &protobuf.CommunityRequestToJoinResponse{ Clock: community.Clock(), Accepted: true, @@ -1437,6 +1451,7 @@ func (m *Messenger) AcceptRequestToJoinCommunity(request *requests.AcceptRequest response := &MessengerResponse{} response.AddCommunity(community) + response.AddRequestToJoinCommunity(requestToJoin) // Activity Center notification notification, err := m.persistence.GetActivityCenterNotificationByID(request.ID) @@ -1522,9 +1537,10 @@ func (m *Messenger) DeclineRequestToJoinCommunity(request *requests.DeclineReque } response := &MessengerResponse{} + dbRequest, err = m.communitiesManager.GetRequestToJoin(request.ID) + response.AddRequestToJoinCommunity(dbRequest) if notification != nil { - dbRequest, err := m.communitiesManager.GetRequestToJoin(request.ID) if err != nil { return nil, err } @@ -2159,6 +2175,26 @@ func (m *Messenger) PendingRequestsToJoinForCommunity(id types.HexBytes) ([]*com return m.communitiesManager.PendingRequestsToJoinForCommunity(id) } +func (m *Messenger) AllPendingRequestsToJoinForCommunity(id types.HexBytes) ([]*communities.RequestToJoin, error) { + pendingRequests, err := m.communitiesManager.PendingRequestsToJoinForCommunity(id) + if err != nil { + return nil, err + } + acceptedPendingRequests, err := m.communitiesManager.AcceptedPendingRequestsToJoinForCommunity(id) + if err != nil { + return nil, err + } + declinedPendingRequests, err := m.communitiesManager.DeclinedPendingRequestsToJoinForCommunity(id) + if err != nil { + return nil, err + } + + pendingRequests = append(pendingRequests, acceptedPendingRequests...) + pendingRequests = append(pendingRequests, declinedPendingRequests...) + + return pendingRequests, nil +} + func (m *Messenger) DeclinedRequestsToJoinForCommunity(id types.HexBytes) ([]*communities.RequestToJoin, error) { return m.communitiesManager.DeclinedRequestsToJoinForCommunity(id) } @@ -2542,6 +2578,7 @@ func (m *Messenger) handleCommunityResponse(state *ReceivedMessageState, communi state.Response.AddCommunity(community) state.Response.CommunityChanges = append(state.Response.CommunityChanges, communityResponse.Changes) + state.Response.AddRequestsToJoinCommunity(communityResponse.RequestsToJoin) // If we haven't joined the org, nothing to do if !community.Joined() { @@ -2608,6 +2645,41 @@ func (m *Messenger) handleCommunityResponse(state *ReceivedMessageState, communi return err } + for _, requestToJoin := range communityResponse.RequestsToJoin { + // Activity Center notification + notification, err := m.persistence.GetActivityCenterNotificationByID(requestToJoin.ID) + if err != nil { + return err + } + + if notification != nil { + notification.MembershipStatus = ActivityCenterMembershipStatusAccepted + switch requestToJoin.State { + case communities.RequestToJoinStateDeclined: + notification.MembershipStatus = ActivityCenterMembershipStatusDeclined + case communities.RequestToJoinStateAccepted: + notification.MembershipStatus = ActivityCenterMembershipStatusAccepted + case communities.RequestToJoinStateAcceptedPending: + notification.MembershipStatus = ActivityCenterMembershipStatusAcceptedPending + case communities.RequestToJoinStateDeclinedPending: + notification.MembershipStatus = ActivityCenterMembershipStatusDeclinedPending + default: + notification.MembershipStatus = ActivityCenterMembershipStatusPending + + } + + notification.Read = true + notification.Accepted = true + notification.UpdatedAt = m.getCurrentTimeInMillis() + + err = m.addActivityCenterNotification(state.Response, notification) + if err != nil { + m.logger.Error("failed to save notification", zap.Error(err)) + return err + } + } + } + return nil } @@ -2639,7 +2711,55 @@ func (m *Messenger) handleCommunityEventsMessageRejected(state *ReceivedMessageS } func (m *Messenger) handleCommunityPrivilegedUserSyncMessage(state *ReceivedMessageState, signer *ecdsa.PublicKey, message protobuf.CommunityPrivilegedUserSyncMessage) error { - return m.communitiesManager.HandleCommunityPrivilegedUserSyncMessage(signer, &message) + if signer == nil { + return errors.New("signer can't be nil") + } + + community, err := m.communitiesManager.GetByID(message.CommunityId) + if err != nil { + return err + } + + if community == nil { + return errors.New("community not found") + } + + if !community.IsPrivilegedMember(&m.identity.PublicKey) { + return errors.New("user has no permissions to process privileged sync message") + } + + isControlNodeMsg := common.IsPubKeyEqual(community.PublicKey(), signer) + if !(isControlNodeMsg || community.IsPrivilegedMember(signer)) { + return errors.New("user has no permissions to send privileged sync message") + } + + err = m.communitiesManager.ValidateCommunityPrivilegedUserSyncMessage(&message) + if err != nil { + return err + } + + switch message.Type { + case protobuf.CommunityPrivilegedUserSyncMessage_CONTROL_NODE_ACCEPT_REQUEST_TO_JOIN: + fallthrough + case protobuf.CommunityPrivilegedUserSyncMessage_CONTROL_NODE_REJECT_REQUEST_TO_JOIN: + if !isControlNodeMsg { + return errors.New("accepted/requested to join sync messages can be send only by the control node") + } + requestsToJoin, err := m.communitiesManager.HandleRequestToJoinPrivilegedUserSyncMessage(&message, community.ID()) + if err != nil { + return nil + } + state.Response.AddRequestsToJoinCommunity(requestsToJoin) + + case protobuf.CommunityPrivilegedUserSyncMessage_ADD_COMMUNITY_TOKENS: + // TODO add tokens to the Response + err = m.communitiesManager.HandleAddCommunityTokenPrivilegedUserSyncMessage(&message, community) + if err != nil { + return nil + } + } + + return nil } func (m *Messenger) handleSyncCommunity(messageState *ReceivedMessageState, syncCommunity protobuf.SyncCommunity) error { diff --git a/services/ext/api.go b/services/ext/api.go index 1f4f60f1b..0f19f3a2f 100644 --- a/services/ext/api.go +++ b/services/ext/api.go @@ -561,6 +561,11 @@ func (api *PublicAPI) PendingRequestsToJoinForCommunity(id types.HexBytes) ([]*c return api.service.messenger.PendingRequestsToJoinForCommunity(id) } +// AllPendingRequestsToJoinForCommunity returns the all the pending requests to join, including accepted and rejected ones, for a given community +func (api *PublicAPI) AllPendingRequestsToJoinForCommunity(id types.HexBytes) ([]*communities.RequestToJoin, error) { + return api.service.messenger.AllPendingRequestsToJoinForCommunity(id) +} + // DeclinedRequestsToJoinForCommunity returns the declined requests to join for a given community func (api *PublicAPI) DeclinedRequestsToJoinForCommunity(id types.HexBytes) ([]*communities.RequestToJoin, error) { return api.service.messenger.DeclinedRequestsToJoinForCommunity(id)