Skip to content

Commit

Permalink
fix_: mitigate permission stuck in pending state
Browse files Browse the repository at this point in the history
This PR mitigates permission stuck in pending state upon making device a
control node. It fixes [#14023](status-im/status-desktop#14023)
  • Loading branch information
kounkou committed May 8, 2024
1 parent a97f1bb commit 3dde1e8
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 10 deletions.
54 changes: 54 additions & 0 deletions protocol/communities/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -5232,11 +5232,65 @@ func (m *Manager) promoteSelfToControlNode(community *Community, clock uint64) (
return false, err
}

err = m.handleCommunityEvents(community)
if err != nil {
return false, err
}

community.increaseClock()

return ownerChanged, nil
}

func (m *Manager) handleCommunityEvents(community *Community) error {
if community.config.EventsData == nil {
return nil
}

lastlyAppliedEvents, err := m.persistence.GetAppliedCommunityEvents(community.ID())
if err != nil {
return err
}

err = community.processEvents(community.toCommunityEventsMessage(), lastlyAppliedEvents)
if err != nil {
return err
}

_, err = m.handleAdditionalAdminChanges(community)
if err != nil {
return err
}

if err = m.handleCommunityTokensMetadata(community); err != nil {
return err
}

appliedEvents := map[string]uint64{}
if community.config.EventsData != nil {
for _, event := range community.config.EventsData.Events {
appliedEvents[event.EventTypeID()] = event.CommunityEventClock
}
}

community.config.EventsData = nil // clear events, they are already applied
community.increaseClock()

err = m.persistence.SaveCommunity(community)
if err != nil {
return err
}

err = m.persistence.UpsertAppliedCommunityEvents(community.ID(), appliedEvents)
if err != nil {
return err
}

m.publish(&Subscription{Community: community})

return nil
}

func (m *Manager) shareRequestsToJoinWithNewPrivilegedMembers(community *Community, newPrivilegedMembers map[protobuf.CommunityMember_Roles][]*ecdsa.PublicKey) error {
requestsToJoin, err := m.GetCommunityRequestsToJoinWithRevealedAddresses(community.ID())
if err != nil {
Expand Down
93 changes: 93 additions & 0 deletions protocol/communities_messenger_signers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -732,3 +732,96 @@ func (s *MessengerCommunitiesSignersSuite) TestSyncTokenGatedCommunity() {
})
}
}

func (s *MessengerCommunitiesSignersSuite) TestWithMintedOwnerTokenApplyCommunityEventsUponMakingDeviceControlNode() {
community := s.createCommunity(s.john)

// john mints owner token
var chainID uint64 = 1
tokenAddress := "token-address"
tokenName := "tokenName"
tokenSymbol := "TSM"
_, err := s.john.SaveCommunityToken(&token.CommunityToken{
TokenType: protobuf.CommunityTokenType_ERC721,
CommunityID: community.IDString(),
Address: tokenAddress,
ChainID: int(chainID),
Name: tokenName,
Supply: &bigint.BigInt{},
Symbol: tokenSymbol,
PrivilegesLevel: token.OwnerLevel,
}, nil)
s.Require().NoError(err)

err = s.john.AddCommunityToken(community.IDString(), int(chainID), tokenAddress)
s.Require().NoError(err)

// Make sure there is no control node
s.Require().False(common.IsPubKeyEqual(community.ControlNode(), &s.john.identity.PublicKey))

// Trick. We need to remove the community private key otherwise the events
// will be signed and Events will be approved instead of being in Pending State.
_, err = s.john.RemovePrivateKey(community.ID())
s.Require().NoError(err)

request := requests.CreateCommunityTokenPermission{
CommunityID: community.ID(),
Type: protobuf.CommunityTokenPermission_BECOME_ADMIN,
TokenCriteria: []*protobuf.TokenCriteria{
&protobuf.TokenCriteria{
Type: protobuf.CommunityTokenType_ERC20,
ContractAddresses: map[uint64]string{testChainID1: "0x123"},
Symbol: "TEST",
AmountInWei: "100000000000000000000",
Decimals: uint64(18),
},
},
}

response, err := s.john.CreateCommunityTokenPermission(&request)
s.Require().NoError(err)
s.Require().Len(response.CommunityChanges, 1)
s.Require().Len(response.CommunityChanges[0].TokenPermissionsAdded, 1)

addedPermission := func() *communities.CommunityTokenPermission {
for _, permission := range response.CommunityChanges[0].TokenPermissionsAdded {
return permission
}
return nil
}()
s.Require().NotNil(addedPermission)
s.Require().Equal(communities.TokenPermissionAdditionPending, addedPermission.State)

messengerReponse, err := s.john.PromoteSelfToControlNode(community.ID())

s.Require().NoError(err)
s.Require().Len(messengerReponse.Communities(), 1)

tokenPermissions := messengerReponse.Communities()[0].TokenPermissions()
s.Require().Len(tokenPermissions, 2)

tokenPermissionsMap := make(map[protobuf.CommunityTokenPermission_Type]struct{}, len(tokenPermissions))
for _, t := range tokenPermissions {
tokenPermissionsMap[t.Type] = struct{}{}
}

s.Require().Len(tokenPermissionsMap, 2)
s.Require().Contains(tokenPermissionsMap, protobuf.CommunityTokenPermission_BECOME_TOKEN_OWNER)
s.Require().Contains(tokenPermissionsMap, protobuf.CommunityTokenPermission_BECOME_ADMIN)

for _, v := range tokenPermissions {
s.Require().Equal(communities.TokenPermissionApproved, v.State)
}
}

func (s *MessengerCommunitiesSignersSuite) TestWithoutMintedOwnerTokenMakingDeviceControlNodeIsBlocked() {
community := s.createCommunity(s.john)

// Make sure there is no control node
s.Require().False(common.IsPubKeyEqual(community.ControlNode(), &s.john.identity.PublicKey))

response, err := s.john.PromoteSelfToControlNode(community.ID())
s.Require().Nil(response)
s.Require().NotNil(err)
s.Require().Error(err, "Owner token is needed")
}
55 changes: 45 additions & 10 deletions protocol/messenger_communities.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,18 @@ const (
maxChunkSizeBytes = 1500000
)

const (
ErrOwnerTokenNeeded = "Owner token is needed" // #nosec G101
ErrMissingCommunityID = "CommunityID has to be provided"
ErrForbiddenProfileOrWatchOnlyAccount = "Cannot join a community using profile chat or watch-only account"
ErrSigningJoinRequestForKeycardAccounts = "Signing a joining community request for accounts migrated to keycard must be done with a keycard"
ErrNotPartOfCommunity = "Not part of the community"
ErrNotAdminOrOwner = "Not admin or owner"
ErrSignerIsNil = "Signer can't be nil"
ErrSyncMessagesSentByNonControlNode = "Accepted/requested to join sync messages can be send only by the control node"
ErrReceiverIsNil = "Receiver can't be nil"
)

type FetchCommunityRequest struct {
// CommunityKey should be either a public or a private community key
CommunityKey string `json:"communityKey"`
Expand Down Expand Up @@ -1175,7 +1187,7 @@ func (m *Messenger) generateCommunityRequestsForSigning(memberPubKey string, com

func (m *Messenger) GenerateJoiningCommunityRequestsForSigning(memberPubKey string, communityID types.HexBytes, addressesToReveal []string) ([]account.SignParams, error) {
if len(communityID) == 0 {
return nil, errors.New("communityID has to be provided")
return nil, errors.New(ErrMissingCommunityID)
}
return m.generateCommunityRequestsForSigning(memberPubKey, communityID, addressesToReveal, false)
}
Expand All @@ -1200,7 +1212,7 @@ func (m *Messenger) SignData(signParams []account.SignParams) ([]string, error)
}

if account.Chat || account.Type == accounts.AccountTypeWatch {
return nil, errors.New("cannot join a community using profile chat or watch-only account")
return nil, errors.New(ErrForbiddenProfileOrWatchOnlyAccount)
}

keypair, err := m.settings.GetKeypairByKeyUID(account.KeyUID)
Expand All @@ -1209,7 +1221,7 @@ func (m *Messenger) SignData(signParams []account.SignParams) ([]string, error)
}

if keypair.MigratedToKeycard() {
return nil, errors.New("signing a joining community request for accounts migrated to keycard must be done with a keycard")
return nil, errors.New(ErrSigningJoinRequestForKeycardAccounts)
}

verifiedAccount, err := m.accountsManager.GetVerifiedWalletAccount(m.settings, param.Address, param.Password)
Expand Down Expand Up @@ -1419,7 +1431,7 @@ func (m *Messenger) EditSharedAddressesForCommunity(request *requests.EditShared
}

if !community.HasMember(m.IdentityPublicKey()) {
return nil, errors.New("not part of the community")
return nil, errors.New(ErrNotPartOfCommunity)
}

revealedAddresses := make([]gethcommon.Address, 0)
Expand Down Expand Up @@ -2332,7 +2344,7 @@ func (m *Messenger) SetCommunityShard(request *requests.SetCommunityShard) (*Mes
}

if !community.IsControlNode() {
return nil, errors.New("not admin or owner")
return nil, errors.New(ErrNotAdminOrOwner)
}

// Reset the community private key
Expand Down Expand Up @@ -2411,7 +2423,7 @@ func (m *Messenger) SetCommunityStorenodes(request *requests.SetCommunityStoreno
return nil, err
}
if !community.IsControlNode() {
return nil, errors.New("not admin or owner")
return nil, errors.New(ErrNotAdminOrOwner)
}

if err := m.communityStorenodes.UpdateStorenodesInDB(request.CommunityID, request.Storenodes, 0); err != nil {
Expand Down Expand Up @@ -3264,7 +3276,7 @@ func (m *Messenger) HandleCommunityShardKey(state *ReceivedMessageState, message

signer := state.CurrentMessageState.PublicKey
if signer == nil {
return errors.New("signer can't be nil")
return errors.New(ErrReceiverIsNil)
}

err = m.handleCommunityShardAndFiltersFromProto(community, message)
Expand Down Expand Up @@ -3324,7 +3336,7 @@ func (m *Messenger) handleCommunityShardAndFiltersFromProto(community *communiti

func (m *Messenger) handleCommunityPrivilegedUserSyncMessage(state *ReceivedMessageState, signer *ecdsa.PublicKey, message *protobuf.CommunityPrivilegedUserSyncMessage) error {
if signer == nil {
return errors.New("signer can't be nil")
return errors.New(ErrSignerIsNil)
}

community, err := m.communitiesManager.GetByID(message.CommunityId)
Expand All @@ -3337,7 +3349,7 @@ func (m *Messenger) handleCommunityPrivilegedUserSyncMessage(state *ReceivedMess
// CONTROL_NODE were sent by a control node
isControlNodeMsg := common.IsPubKeyEqual(community.ControlNode(), signer)
if !isControlNodeMsg {
return errors.New("accepted/requested to join sync messages can be send only by the control node")
return errors.New(ErrSyncMessagesSentByNonControlNode)
}

err = m.communitiesManager.ValidateCommunityPrivilegedUserSyncMessage(message)
Expand Down Expand Up @@ -3373,7 +3385,7 @@ func (m *Messenger) HandleCommunityPrivilegedUserSyncMessage(state *ReceivedMess

func (m *Messenger) sendSharedAddressToControlNode(receiver *ecdsa.PublicKey, community *communities.Community) (*communities.RequestToJoin, error) {
if receiver == nil {
return nil, errors.New("receiver can't be nil")
return nil, errors.New(ErrReceiverIsNil)
}

if community == nil {
Expand Down Expand Up @@ -4487,7 +4499,30 @@ func (m *Messenger) processCommunityChanges(messageState *ReceivedMessageState)
messageState.Response.CommunityChanges = nil
}

func (m *Messenger) communityContainsOwnerToken() (bool, error) {
tokens, err := m.GetAllCommunityTokens()
if err != nil {
return false, err
}

for _, t := range tokens {
if t.PrivilegesLevel == token.OwnerLevel {
return true, nil
}
}

return false, nil
}

func (m *Messenger) PromoteSelfToControlNode(communityID types.HexBytes) (*MessengerResponse, error) {
containsOwnerToken, err := m.communityContainsOwnerToken()
if err != nil {
return nil, err
}
if !containsOwnerToken {
return nil, errors.New(ErrOwnerTokenNeeded)
}

clock, _ := m.getLastClockWithRelatedChat()

community, err := m.FetchCommunity(&FetchCommunityRequest{
Expand Down

0 comments on commit 3dde1e8

Please sign in to comment.