Skip to content

Commit

Permalink
Merge pull request #69 from onflow/ls/improvement/access-checker
Browse files Browse the repository at this point in the history
[LS] Implement account access check
  • Loading branch information
sideninja authored Jan 20, 2023
2 parents a6dc82f + 76e02ad commit aa05d4c
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 0 deletions.
10 changes: 10 additions & 0 deletions languageserver/integration/flow.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ type flowClient interface {
) (*flow.Transaction, *flow.TransactionResult, error)
GetAccount(address flow.Address) (*flow.Account, error)
CreateAccount() (*clientAccount, error)
getState() *flowkit.State
getConfigPath() string
}

var _ flowClient = &flowkitClient{}
Expand Down Expand Up @@ -130,6 +132,14 @@ func (f *flowkitClient) Initialize(configPath string, numberOfAccounts int) erro
return nil
}

func (f *flowkitClient) getState() *flowkit.State {
return f.state
}

func (f *flowkitClient) getConfigPath() string {
return f.configPath
}

func (f *flowkitClient) Reload() error {
state, err := flowkit.Load([]string{f.configPath}, f.loader)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions languageserver/integration/integration.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ func NewFlowIntegration(s *server.Server, enableFlowClient bool) (*FlowIntegrati
server.WithCodeLensProvider(integration.codeLenses),
server.WithAddressImportResolver(resolve.addressImport),
server.WithAddressContractNamesResolver(resolve.addressContractNames),
server.WithMemberAccountAccessHandler(resolve.accountAccess),
)

comm := commands{client: client}
Expand Down
32 changes: 32 additions & 0 deletions languageserver/integration/mock_flow_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 32 additions & 0 deletions languageserver/integration/resolvers.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@ package integration

import (
"github.com/onflow/cadence/runtime/common"
"github.com/onflow/cadence/runtime/sema"
"github.com/onflow/flow-cli/pkg/flowkit"
"github.com/onflow/flow-cli/pkg/flowkit/config"
"github.com/onflow/flow-go-sdk"
"path/filepath"
"strings"
)

Expand All @@ -30,6 +33,7 @@ type resolvers struct {
loader flowkit.ReaderWriter
}

// fileImport loads the code for a string location.
func (r *resolvers) fileImport(location common.StringLocation) (string, error) {
filename := cleanWindowsPath(location.String())

Expand All @@ -41,6 +45,7 @@ func (r *resolvers) fileImport(location common.StringLocation) (string, error) {
return string(data), nil
}

// addressImport loads the code for an address location.
func (r *resolvers) addressImport(location common.AddressLocation) (string, error) {
account, err := r.client.GetAccount(flow.HexToAddress(location.Address.String()))
if err != nil {
Expand All @@ -50,6 +55,7 @@ func (r *resolvers) addressImport(location common.AddressLocation) (string, erro
return string(account.Contracts[location.Name]), nil
}

// addressContractNames returns a slice of all the contract names on the address location.
func (r *resolvers) addressContractNames(address common.Address) ([]string, error) {
account, err := r.client.GetAccount(flow.HexToAddress(address.String()))
if err != nil {
Expand All @@ -66,6 +72,32 @@ func (r *resolvers) addressContractNames(address common.Address) ([]string, erro
return names, nil
}

// accountAccess checks whether the current program location and accessed program location were deployed to the same account.
//
// if the contracts were deployed on the same account then it returns true and hence allows the access, false otherwise.
func (r *resolvers) accountAccess(checker *sema.Checker, memberLocation common.Location) bool {
contracts, err := r.client.getState().DeploymentContractsByNetwork(config.DefaultEmulatorNetwork().Name)
if err != nil {
return false
}

var checkerAccount, memberAccount string
// go over contracts and match contract by the location of checker and member and assign the account name for later check
for _, c := range contracts {
// get absolute path of the contract relative to the dir where flow.json is (working env)
absLocation, _ := filepath.Abs(filepath.Join(filepath.Dir(r.client.getConfigPath()), c.Source))

if memberLocation.String() == absLocation {
memberAccount = c.AccountName
}
if checker.Location.String() == absLocation {
checkerAccount = c.AccountName
}
}

return checkerAccount == memberAccount && checkerAccount != "" && memberAccount != ""
}

// workaround for Windows files being sent with prefixed '/' which is /c:/test/foo
// we remove the first / for Windows files, so they are valid, also replace encoded column sign.
func cleanWindowsPath(path string) string {
Expand Down
13 changes: 13 additions & 0 deletions languageserver/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,19 @@ func WithInitializationOptionsHandler(handler InitializationOptionsHandler) Opti
}
}

// WithMemberAccountAccessHandler returns a server option that adds the given function
// as a function that is used to determine access for accounts.
//
// When we have a syntax like access(account) this handler is called and
// determines whether the access is allowed based on the location of program and the called member.
func WithMemberAccountAccessHandler(handler sema.MemberAccountAccessHandlerFunc) Option {
return func(server *Server) error {
server.checkerStandardConfig.MemberAccountAccessHandler = handler
server.checkerScriptConfig.MemberAccountAccessHandler = handler
return nil
}
}

const GetEntryPointParametersCommand = "cadence.server.getEntryPointParameters"
const GetContractInitializerParametersCommand = "cadence.server.getContractInitializerParameters"
const ParseEntryPointArgumentsCommand = "cadence.server.parseEntryPointArguments"
Expand Down

0 comments on commit aa05d4c

Please sign in to comment.