fix_: mitigate permission stuck in pending state (#5070)

This PR mitigates permission stuck in pending state upon making device a
control node. It fixes [#14023](status-im/status-desktop#14023)
This commit is contained in:
Godfrain Jacques 2024-05-10 08:56:40 -07:00 committed by GitHub
parent cb4c19cece
commit 627e23ffa5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 184 additions and 19 deletions

View File

@ -2107,20 +2107,12 @@ func (m *Manager) HandleCommunityEventsMessage(signer *ecdsa.PublicKey, message
return nil, err
}
}
err = community.processEvents(eventsMessage, lastlyAppliedEvents)
if err != nil {
return nil, err
}
additionalCommunityResponse, err := m.handleAdditionalAdminChanges(community)
additionalCommunityResponse, err := m.handleCommunityEventsAndMetadata(community, eventsMessage, lastlyAppliedEvents)
if err != nil {
return nil, err
}
if err = m.handleCommunityTokensMetadata(community); err != nil {
return nil, err
}
// Control node applies events and publish updated CommunityDescription
if community.IsControlNode() {
appliedEvents := map[string]uint64{}
@ -5289,11 +5281,75 @@ 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) handleCommunityEventsAndMetadata(community *Community, eventsMessage *CommunityEventsMessage,
lastlyAppliedEvents map[string]uint64) (*CommunityResponse, error) {
err := community.processEvents(eventsMessage, lastlyAppliedEvents)
if err != nil {
return nil, err
}
additionalCommunityResponse, err := m.handleAdditionalAdminChanges(community)
if err != nil {
return nil, err
}
if err = m.handleCommunityTokensMetadata(community); err != nil {
return nil, err
}
return additionalCommunityResponse, err
}
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 = m.handleCommunityEventsAndMetadata(community, community.toCommunityEventsMessage(), lastlyAppliedEvents)
if 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 {

View File

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

View File

@ -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"`
@ -1229,7 +1241,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)
}
@ -1254,7 +1266,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)
@ -1263,7 +1275,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)
@ -1473,7 +1485,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)
@ -2386,7 +2398,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
@ -2465,7 +2477,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 {
@ -3318,7 +3330,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)
@ -3378,7 +3390,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)
@ -3391,7 +3403,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)
@ -3427,7 +3439,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 {
@ -4555,6 +4567,10 @@ func (m *Messenger) PromoteSelfToControlNode(communityID types.HexBytes) (*Messe
return nil, err
}
if !communities.HasTokenOwnership(community.Description()) {
return nil, errors.New(ErrOwnerTokenNeeded)
}
changes, err := m.communitiesManager.PromoteSelfToControlNode(community, clock)
if err != nil {
return nil, err