Fix pending join requests + API to get them (#3902)

Needed for https://github.com/status-im/status-desktop/issues/11851
This commit is contained in:
Jonathan Rainville 2023-08-18 15:52:13 -04:00 committed by GitHub
parent 5272f99b59
commit 3bf0bed78d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 213 additions and 96 deletions

View File

@ -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")
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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)