extargs: add ".inf" external args for arbitrary Infura args
This commit is contained in:
committed by
Michael Muré
parent
09c8815755
commit
3b6d70f47a
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/INFURA/go-ethlibs/jsonrpc"
|
||||
"github.com/ipfs/go-cid"
|
||||
"github.com/ipld/go-ipld-prime/datamodel"
|
||||
"github.com/ucan-wg/go-ucan/pkg/args"
|
||||
"github.com/ucan-wg/go-ucan/pkg/command"
|
||||
"github.com/ucan-wg/go-ucan/pkg/container"
|
||||
@@ -34,6 +35,7 @@ type UcanCtx struct {
|
||||
// argument sources
|
||||
http *extargs.HttpExtArgs
|
||||
jsonrpc *extargs.JsonRpcExtArgs
|
||||
infura *extargs.InfuraExtArgs
|
||||
}
|
||||
|
||||
func FromContainer(cont container.Reader) (*UcanCtx, error) {
|
||||
@@ -99,6 +101,7 @@ func (ctn UcanCtx) Meta() meta.ReadOnly {
|
||||
}
|
||||
|
||||
// VerifyHttp verify the delegation's policies against arguments constructed from the HTTP request.
|
||||
// These arguments will be set in the `.http` argument key, at the root.
|
||||
// This function can only be called once per context.
|
||||
// After being used, those constructed arguments will be used in ExecutionAllowed as well.
|
||||
func (ctn UcanCtx) VerifyHttp(req *http.Request) error {
|
||||
@@ -110,6 +113,7 @@ func (ctn UcanCtx) VerifyHttp(req *http.Request) error {
|
||||
}
|
||||
|
||||
// VerifyJsonRpc verify the delegation's policies against arguments constructed from the JsonRpc request.
|
||||
// These arguments will be set in the `.jsonrpc` argument key, at the root.
|
||||
// This function can only be called once per context.
|
||||
// After being used, those constructed arguments will be used in ExecutionAllowed as well.
|
||||
func (ctn UcanCtx) VerifyJsonRpc(req *jsonrpc.Request) error {
|
||||
@@ -120,6 +124,18 @@ func (ctn UcanCtx) VerifyJsonRpc(req *jsonrpc.Request) error {
|
||||
return ctn.jsonrpc.Verify()
|
||||
}
|
||||
|
||||
// VerifyInfura verify the delegation's policies against arbitrary arguments provider through an IPLD MapAssembler.
|
||||
// These arguments will be set in the `.inf` argument key, at the root.
|
||||
// This function can only be called once per context.
|
||||
// After being used, those constructed arguments will be used in ExecutionAllowed as well.
|
||||
func (ctn UcanCtx) VerifyInfura(assembler func(ma datamodel.MapAssembler)) error {
|
||||
if ctn.infura != nil {
|
||||
panic("only use once per request context")
|
||||
}
|
||||
ctn.infura = extargs.NewInfuraExtArgs(ctn.policies, assembler)
|
||||
return ctn.infura.Verify()
|
||||
}
|
||||
|
||||
// ExecutionAllowed does the final verification of the invocation.
|
||||
// If VerifyHttp or VerifyJsonRpc has been used, those arguments are part of the verification.
|
||||
func (ctn UcanCtx) ExecutionAllowed() error {
|
||||
@@ -140,6 +156,13 @@ func (ctn UcanCtx) ExecutionAllowed() error {
|
||||
}
|
||||
newArgs.Include(jsonRpcArgs)
|
||||
}
|
||||
if ctn.infura != nil {
|
||||
infuraArgs, err := ctn.infura.Args()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newArgs.Include(infuraArgs)
|
||||
}
|
||||
|
||||
return newArgs, nil
|
||||
})
|
||||
|
||||
@@ -107,15 +107,15 @@ func TestHttp(t *testing.T) {
|
||||
// we don't test the args hash here
|
||||
emptyArgs := args.New().ReadOnly()
|
||||
|
||||
ctx := NewHttpExtArgs(pol, emptyArgs, r)
|
||||
extArgs := NewHttpExtArgs(pol, emptyArgs, r)
|
||||
|
||||
_, err := ctx.Args()
|
||||
_, err := extArgs.Args()
|
||||
require.NoError(t, err)
|
||||
|
||||
if tc.expected {
|
||||
require.NoError(t, ctx.Verify())
|
||||
require.NoError(t, extArgs.Verify())
|
||||
} else {
|
||||
require.Error(t, ctx.Verify())
|
||||
require.Error(t, extArgs.Verify())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,12 +173,12 @@ func TestHttpHash(t *testing.T) {
|
||||
err := invArgs.Add(HttpArgsKey, tc.hash)
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx := NewHttpExtArgs(pol, invArgs.ReadOnly(), req)
|
||||
extArgs := NewHttpExtArgs(pol, invArgs.ReadOnly(), req)
|
||||
|
||||
if tc.expected {
|
||||
require.NoError(t, ctx.Verify())
|
||||
require.NoError(t, extArgs.Verify())
|
||||
} else {
|
||||
require.Error(t, ctx.Verify())
|
||||
require.Error(t, extArgs.Verify())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
85
toolkit/server/extargs/infura.go
Normal file
85
toolkit/server/extargs/infura.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package extargs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/ipld/go-ipld-prime"
|
||||
"github.com/ipld/go-ipld-prime/datamodel"
|
||||
"github.com/ipld/go-ipld-prime/fluent/qp"
|
||||
"github.com/ipld/go-ipld-prime/node/basicnode"
|
||||
"github.com/ucan-wg/go-ucan/pkg/args"
|
||||
"github.com/ucan-wg/go-ucan/pkg/policy"
|
||||
)
|
||||
|
||||
const InfuraArgsKey = "inf"
|
||||
|
||||
type InfuraExtArgs struct {
|
||||
pol policy.Policy
|
||||
originalArgs args.ReadOnly
|
||||
assembler func(ma datamodel.MapAssembler)
|
||||
|
||||
once sync.Once
|
||||
args *args.Args
|
||||
argsIpld ipld.Node
|
||||
}
|
||||
|
||||
func NewInfuraExtArgs(pol policy.Policy, assembler func(ma datamodel.MapAssembler)) *InfuraExtArgs {
|
||||
return &InfuraExtArgs{pol: pol, assembler: assembler}
|
||||
}
|
||||
|
||||
func (ia *InfuraExtArgs) Verify() error {
|
||||
if err := ia.makeArgs(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Note: InfuraExtArgs doesn't support verifying a hash computed client-side like the other
|
||||
// external args, as the arguments are by nature dynamic. The client can't generate a meaningful hash.
|
||||
|
||||
ok, leaf := ia.pol.PartialMatch(ia.argsIpld)
|
||||
if !ok {
|
||||
return fmt.Errorf("the following UCAN policy is not satisfied: %v", leaf.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ia *InfuraExtArgs) Args() (*args.Args, error) {
|
||||
if err := ia.makeArgs(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ia.args, nil
|
||||
}
|
||||
|
||||
func (ia *InfuraExtArgs) makeArgs() error {
|
||||
var outerErr error
|
||||
ia.once.Do(func() {
|
||||
var err error
|
||||
|
||||
ia.args, err = makeInfuraArgs(ia.assembler)
|
||||
if err != nil {
|
||||
outerErr = err
|
||||
return
|
||||
}
|
||||
|
||||
ia.argsIpld, err = ia.args.ToIPLD()
|
||||
if err != nil {
|
||||
outerErr = err
|
||||
return
|
||||
}
|
||||
})
|
||||
return outerErr
|
||||
}
|
||||
|
||||
func makeInfuraArgs(assembler func(ma datamodel.MapAssembler)) (*args.Args, error) {
|
||||
n, err := qp.BuildMap(basicnode.Prototype.Any, -1, assembler)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := args.New()
|
||||
err = res.Add(InfuraArgsKey, n)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
114
toolkit/server/extargs/infura_test.go
Normal file
114
toolkit/server/extargs/infura_test.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package extargs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/ipld/go-ipld-prime/datamodel"
|
||||
"github.com/ipld/go-ipld-prime/fluent/qp"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/ucan-wg/go-ucan/pkg/policy"
|
||||
"github.com/ucan-wg/go-ucan/pkg/policy/literal"
|
||||
)
|
||||
|
||||
func ExampleInfuraExtArgs() {
|
||||
// Note: this is an example for how to build arguments, but you likely want to use InfuraExtArgs
|
||||
// through UcanCtx.
|
||||
|
||||
pol := policy.Policy{} // policies from the delegations
|
||||
|
||||
// We will construct the following args:
|
||||
// {
|
||||
// "ntwk":"eth-mainnet",
|
||||
// "quota":{
|
||||
// "ur":1234,
|
||||
// "udc":1234,
|
||||
// "arch":1234,
|
||||
// "down":1234,
|
||||
// "store":1234,
|
||||
// "up":1234
|
||||
// }
|
||||
// }
|
||||
infArgs := NewInfuraExtArgs(pol, func(ma datamodel.MapAssembler) {
|
||||
qp.MapEntry(ma, "ntwk", qp.String("eth-mainnet"))
|
||||
qp.MapEntry(ma, "quota", qp.Map(6, func(ma datamodel.MapAssembler) {
|
||||
qp.MapEntry(ma, "ur", qp.Int(1234))
|
||||
qp.MapEntry(ma, "udc", qp.Int(1234))
|
||||
qp.MapEntry(ma, "arch", qp.Int(1234))
|
||||
qp.MapEntry(ma, "down", qp.Int(1234))
|
||||
qp.MapEntry(ma, "store", qp.Int(1234))
|
||||
qp.MapEntry(ma, "up", qp.Int(1234))
|
||||
}))
|
||||
})
|
||||
|
||||
err := infArgs.Verify()
|
||||
fmt.Println(err)
|
||||
|
||||
// Output:
|
||||
// <nil>
|
||||
}
|
||||
|
||||
func TestInfura(t *testing.T) {
|
||||
assembler := func(ma datamodel.MapAssembler) {
|
||||
qp.MapEntry(ma, "ntwk", qp.String("eth-mainnet"))
|
||||
qp.MapEntry(ma, "quota", qp.Map(6, func(ma datamodel.MapAssembler) {
|
||||
qp.MapEntry(ma, "ur", qp.Int(1234))
|
||||
qp.MapEntry(ma, "udc", qp.Int(1234))
|
||||
qp.MapEntry(ma, "arch", qp.Int(1234))
|
||||
qp.MapEntry(ma, "down", qp.Int(1234))
|
||||
qp.MapEntry(ma, "store", qp.Int(1234))
|
||||
qp.MapEntry(ma, "up", qp.Int(1234))
|
||||
}))
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
pol policy.Policy
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "no policies",
|
||||
pol: policy.Policy{},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "matching args",
|
||||
pol: policy.MustConstruct(
|
||||
policy.Equal(".inf.ntwk", literal.String("eth-mainnet")),
|
||||
policy.LessThanOrEqual(".inf.quota.ur", literal.Int(1234)),
|
||||
),
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "wrong network",
|
||||
pol: policy.MustConstruct(
|
||||
policy.Equal(".inf.ntwk", literal.String("avalanche-fuji")),
|
||||
policy.LessThanOrEqual(".inf.quota.ur", literal.Int(1234)),
|
||||
),
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "unrespected quota",
|
||||
pol: policy.MustConstruct(
|
||||
policy.Equal(".inf.ntwk", literal.String("eth-mainnet")),
|
||||
policy.LessThanOrEqual(".inf.quota.ur", literal.Int(100)),
|
||||
),
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
extArgs := NewInfuraExtArgs(tc.pol, assembler)
|
||||
|
||||
_, err := extArgs.Args()
|
||||
require.NoError(t, err)
|
||||
|
||||
if tc.expected {
|
||||
require.NoError(t, extArgs.Verify())
|
||||
} else {
|
||||
require.Error(t, extArgs.Verify())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -97,15 +97,15 @@ func TestJsonRpc(t *testing.T) {
|
||||
// we don't test the args hash here
|
||||
emptyArgs := args.New().ReadOnly()
|
||||
|
||||
ctx := NewJsonRpcExtArgs(tc.pol, emptyArgs, tc.req)
|
||||
extArgs := NewJsonRpcExtArgs(tc.pol, emptyArgs, tc.req)
|
||||
|
||||
_, err := ctx.Args()
|
||||
_, err := extArgs.Args()
|
||||
require.NoError(t, err)
|
||||
|
||||
if tc.expected {
|
||||
require.NoError(t, ctx.Verify())
|
||||
require.NoError(t, extArgs.Verify())
|
||||
} else {
|
||||
require.Error(t, ctx.Verify())
|
||||
require.Error(t, extArgs.Verify())
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -152,12 +152,12 @@ func TestJsonRpcHash(t *testing.T) {
|
||||
err := invArgs.Add(JsonRpcArgsKey, tc.hash)
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx := NewJsonRpcExtArgs(pol, invArgs.ReadOnly(), req)
|
||||
extArgs := NewJsonRpcExtArgs(pol, invArgs.ReadOnly(), req)
|
||||
|
||||
if tc.expected {
|
||||
require.NoError(t, ctx.Verify())
|
||||
require.NoError(t, extArgs.Verify())
|
||||
} else {
|
||||
require.Error(t, ctx.Verify())
|
||||
require.Error(t, extArgs.Verify())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user