Add permission checks for ENS token permissions

Also add tests for creating and editing token permissions
as well as a test for checking ENS ownership.
This commit is contained in:
Pascal Precht 2023-05-04 17:40:52 +02:00 committed by Follow the white rabbit
parent 92b5d831fe
commit ffc1006953
5 changed files with 204 additions and 1 deletions

View File

@ -11,6 +11,7 @@ import (
ens "github.com/wealdtech/go-ens/v3" ens "github.com/wealdtech/go-ens/v3"
"go.uber.org/zap" "go.uber.org/zap"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethclient"
"github.com/status-im/status-go/eth-node/crypto" "github.com/status-im/status-go/eth-node/crypto"
@ -30,6 +31,17 @@ func NewVerifier(logger *zap.Logger) *Verifier {
return &Verifier{logger: logger} return &Verifier{logger: logger}
} }
func (m *Verifier) ReverseResolve(address common.Address, rpcEndpoint string) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), contractQueryTimeout)
defer cancel()
ethClient, err := ethclient.DialContext(ctx, rpcEndpoint)
if err != nil {
return "", err
}
return ens.ReverseResolve(ethClient, address)
}
func (m *Verifier) verifyENSName(ensInfo enstypes.ENSDetails, ethclient *ethclient.Client) enstypes.ENSResponse { func (m *Verifier) verifyENSName(ensInfo enstypes.ENSDetails, ethclient *ethclient.Client) enstypes.ENSResponse {
publicKeyStr := ensInfo.PublicKeyString publicKeyStr := ensInfo.PublicKeyString
ensName := ensInfo.Name ensName := ensInfo.Name

View File

@ -1,10 +1,15 @@
package enstypes package enstypes
import "crypto/ecdsa" import (
"crypto/ecdsa"
"github.com/ethereum/go-ethereum/common"
)
type ENSVerifier interface { type ENSVerifier interface {
// CheckBatch verifies that a registered ENS name matches the expected public key // CheckBatch verifies that a registered ENS name matches the expected public key
CheckBatch(ensDetails []ENSDetails, rpcEndpoint, contractAddress string) (map[string]ENSResponse, error) CheckBatch(ensDetails []ENSDetails, rpcEndpoint, contractAddress string) (map[string]ENSResponse, error)
ReverseResolve(address common.Address, rpcEndpoint string) (string, error)
} }
type ENSDetails struct { type ENSDetails struct {

View File

@ -1632,6 +1632,11 @@ func (m *Manager) checkPermissionToJoin(permissions []*protobuf.CommunityTokenPe
return response, err return response, err
} }
ownedENSNames, err := m.getOwnedENS(walletAddresses)
if err != nil {
return response, err
}
for _, tokenPermission := range permissions { for _, tokenPermission := range permissions {
permissionRequirementsMet := true permissionRequirementsMet := true
@ -1685,6 +1690,25 @@ func (m *Manager) checkPermissionToJoin(permissions []*protobuf.CommunityTokenPe
if ownedERC20Tokens[tokenRequirement.Symbol].Cmp(big.NewFloat(amount)) != -1 { if ownedERC20Tokens[tokenRequirement.Symbol].Cmp(big.NewFloat(amount)) != -1 {
tokenRequirementMet = true tokenRequirementMet = true
} }
} else if tokenRequirement.Type == protobuf.CommunityTokenType_ENS {
if len(ownedENSNames) == 0 {
response.Permissions[tokenPermission.Id].Criteria = append(response.Permissions[tokenPermission.Id].Criteria, false)
continue
}
if !strings.HasPrefix(tokenRequirement.EnsPattern, "*.") {
for _, ownedENS := range ownedENSNames {
if ownedENS == tokenRequirement.EnsPattern {
tokenRequirementMet = true
}
}
} else {
parentName := tokenRequirement.EnsPattern[2:]
for _, ownedENS := range ownedENSNames {
if strings.HasSuffix(ownedENS, parentName) {
tokenRequirementMet = true
}
}
}
} }
if !tokenRequirementMet { if !tokenRequirementMet {
permissionRequirementsMet = false permissionRequirementsMet = false
@ -1815,6 +1839,24 @@ func (m *Manager) getAccumulatedTokenBalances(accounts []gethcommon.Address, tok
return accumulatedBalances, nil return accumulatedBalances, nil
} }
func (m *Manager) getOwnedENS(addresses []gethcommon.Address) ([]string, error) {
ownedENS := make([]string, 0)
if m.ensVerifier == nil {
m.logger.Warn("no ensVerifier configured for communities manager")
return ownedENS, nil
}
for _, address := range addresses {
name, err := m.ensVerifier.ReverseResolve(address)
if err != nil && err.Error() != "not a resolver" {
return ownedENS, err
}
if name != "" {
ownedENS = append(ownedENS, name)
}
}
return ownedENS, nil
}
func (m *Manager) HandleCommunityRequestToJoinResponse(signer *ecdsa.PublicKey, request *protobuf.CommunityRequestToJoinResponse) (*RequestToJoin, error) { func (m *Manager) HandleCommunityRequestToJoinResponse(signer *ecdsa.PublicKey, request *protobuf.CommunityRequestToJoinResponse) (*RequestToJoin, error) {
pkString := common.PubkeyToHex(&m.identity.PublicKey) pkString := common.PubkeyToHex(&m.identity.PublicKey)

View File

@ -2211,6 +2211,144 @@ func (s *MessengerCommunitiesSuite) TestDeclineAccess() {
s.Require().Len(requestsToJoin, 0) s.Require().Len(requestsToJoin, 0)
} }
func (s *MessengerCommunitiesSuite) TestCreateTokenPermission() {
community := s.createCommunity()
createTokenPermission := &requests.CreateCommunityTokenPermission{
CommunityID: community.ID(),
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
TokenCriteria: []*protobuf.TokenCriteria{
&protobuf.TokenCriteria{
Type: protobuf.CommunityTokenType_ERC20,
ContractAddresses: map[uint64]string{uint64(1): "0x123"},
Symbol: "TEST",
Amount: "100",
Decimals: uint64(18),
},
},
}
response, err := s.admin.CreateCommunityTokenPermission(createTokenPermission)
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.Communities(), 1)
tokenPermissions := response.Communities()[0].TokenPermissions()
for _, tokenPermission := range tokenPermissions {
for _, tc := range tokenPermission.TokenCriteria {
s.Require().Equal(tc.Type, protobuf.CommunityTokenType_ERC20)
s.Require().Equal(tc.Symbol, "TEST")
s.Require().Equal(tc.Amount, "100")
s.Require().Equal(tc.Decimals, uint64(18))
}
}
}
func (s *MessengerCommunitiesSuite) TestEditTokenPermission() {
community := s.createCommunity()
tokenPermission := &requests.CreateCommunityTokenPermission{
CommunityID: community.ID(),
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
TokenCriteria: []*protobuf.TokenCriteria{
&protobuf.TokenCriteria{
Type: protobuf.CommunityTokenType_ERC20,
ContractAddresses: map[uint64]string{uint64(1): "0x123"},
Symbol: "TEST",
Amount: "100",
Decimals: uint64(18),
},
},
}
response, err := s.admin.CreateCommunityTokenPermission(tokenPermission)
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.Communities(), 1)
tokenPermissions := response.Communities()[0].TokenPermissions()
var tokenPermissionID string
for id := range tokenPermissions {
tokenPermissionID = id
}
tokenPermission.TokenCriteria[0].Symbol = "TESTUpdated"
tokenPermission.TokenCriteria[0].Amount = "200"
tokenPermission.TokenCriteria[0].Decimals = uint64(20)
editTokenPermission := &requests.EditCommunityTokenPermission{
PermissionID: tokenPermissionID,
CreateCommunityTokenPermission: *tokenPermission,
}
response2, err := s.admin.EditCommunityTokenPermission(editTokenPermission)
s.Require().NoError(err)
// wait for `checkMemberPermissions` to finish
time.Sleep(1 * time.Second)
s.Require().NotNil(response2)
s.Require().Len(response2.Communities(), 1)
tokenPermissions = response2.Communities()[0].TokenPermissions()
for _, tokenPermission := range tokenPermissions {
for _, tc := range tokenPermission.TokenCriteria {
s.Require().Equal(tc.Type, protobuf.CommunityTokenType_ERC20)
s.Require().Equal(tc.Symbol, "TESTUpdated")
s.Require().Equal(tc.Amount, "200")
s.Require().Equal(tc.Decimals, uint64(20))
}
}
}
func (s *MessengerCommunitiesSuite) TestRequestAccessWithENSTokenPermission() {
community := s.createCommunity()
createTokenPermission := &requests.CreateCommunityTokenPermission{
CommunityID: community.ID(),
Type: protobuf.CommunityTokenPermission_BECOME_MEMBER,
TokenCriteria: []*protobuf.TokenCriteria{
&protobuf.TokenCriteria{
Type: protobuf.CommunityTokenType_ENS,
EnsPattern: "test.stateofus.eth",
},
},
}
response, err := s.admin.CreateCommunityTokenPermission(createTokenPermission)
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.Communities(), 1)
s.advertiseCommunityTo(community, s.alice)
requestToJoin := &requests.RequestToJoinCommunity{CommunityID: community.ID()}
// We try to join the org
response, err = s.alice.RequestToJoinCommunity(requestToJoin)
s.Require().NoError(err)
s.Require().NotNil(response)
s.Require().Len(response.RequestsToJoinCommunity, 1)
requestToJoin1 := response.RequestsToJoinCommunity[0]
s.Require().Equal(communities.RequestToJoinStatePending, requestToJoin1.State)
// Retrieve request to join
err = tt.RetryWithBackOff(func() error {
response, err = s.admin.RetrieveAll()
return err
})
s.Require().NoError(err)
// We don't expect a requestToJoin in the response because due
// to missing revealed wallet addresses, the request should've
// been declined right away
s.Require().Len(response.RequestsToJoinCommunity, 0)
// Ensure alice is not a member of the community
allCommunities, err := s.admin.Communities()
if bytes.Equal(allCommunities[0].ID(), community.ID()) {
s.Require().False(allCommunities[0].HasMember(&s.alice.identity.PublicKey))
}
}
func (s *MessengerCommunitiesSuite) TestLeaveAndRejoinCommunity() { func (s *MessengerCommunitiesSuite) TestLeaveAndRejoinCommunity() {
community := s.createCommunity() community := s.createCommunity()
s.advertiseCommunityTo(community, s.alice) s.advertiseCommunityTo(community, s.alice)

View File

@ -6,6 +6,7 @@ import (
"go.uber.org/zap" "go.uber.org/zap"
gethcommon "github.com/ethereum/go-ethereum/common"
"github.com/status-im/status-go/eth-node/types" "github.com/status-im/status-go/eth-node/types"
enstypes "github.com/status-im/status-go/eth-node/types/ens" enstypes "github.com/status-im/status-go/eth-node/types/ens"
"github.com/status-im/status-go/protocol/common" "github.com/status-im/status-go/protocol/common"
@ -129,6 +130,11 @@ func (v *Verifier) publish(records []*VerificationRecord) {
} }
func (v *Verifier) ReverseResolve(address gethcommon.Address) (string, error) {
verifier := v.node.NewENSVerifier(v.logger)
return verifier.ReverseResolve(address, v.rpcEndpoint)
}
// Verify verifies that a registered ENS name matches the expected public key // Verify verifies that a registered ENS name matches the expected public key
func (v *Verifier) verify(rpcEndpoint, contractAddress string) error { func (v *Verifier) verify(rpcEndpoint, contractAddress string) error {
v.logger.Debug("verifying ENS Names", zap.String("endpoint", rpcEndpoint)) v.logger.Debug("verifying ENS Names", zap.String("endpoint", rpcEndpoint))