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/INFURA/go-ethlibs/jsonrpc"
|
||||||
"github.com/ipfs/go-cid"
|
"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/args"
|
||||||
"github.com/ucan-wg/go-ucan/pkg/command"
|
"github.com/ucan-wg/go-ucan/pkg/command"
|
||||||
"github.com/ucan-wg/go-ucan/pkg/container"
|
"github.com/ucan-wg/go-ucan/pkg/container"
|
||||||
@@ -34,6 +35,7 @@ type UcanCtx struct {
|
|||||||
// argument sources
|
// argument sources
|
||||||
http *extargs.HttpExtArgs
|
http *extargs.HttpExtArgs
|
||||||
jsonrpc *extargs.JsonRpcExtArgs
|
jsonrpc *extargs.JsonRpcExtArgs
|
||||||
|
infura *extargs.InfuraExtArgs
|
||||||
}
|
}
|
||||||
|
|
||||||
func FromContainer(cont container.Reader) (*UcanCtx, error) {
|
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.
|
// 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.
|
// This function can only be called once per context.
|
||||||
// After being used, those constructed arguments will be used in ExecutionAllowed as well.
|
// After being used, those constructed arguments will be used in ExecutionAllowed as well.
|
||||||
func (ctn UcanCtx) VerifyHttp(req *http.Request) error {
|
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.
|
// 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.
|
// This function can only be called once per context.
|
||||||
// After being used, those constructed arguments will be used in ExecutionAllowed as well.
|
// After being used, those constructed arguments will be used in ExecutionAllowed as well.
|
||||||
func (ctn UcanCtx) VerifyJsonRpc(req *jsonrpc.Request) error {
|
func (ctn UcanCtx) VerifyJsonRpc(req *jsonrpc.Request) error {
|
||||||
@@ -120,6 +124,18 @@ func (ctn UcanCtx) VerifyJsonRpc(req *jsonrpc.Request) error {
|
|||||||
return ctn.jsonrpc.Verify()
|
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.
|
// ExecutionAllowed does the final verification of the invocation.
|
||||||
// If VerifyHttp or VerifyJsonRpc has been used, those arguments are part of the verification.
|
// If VerifyHttp or VerifyJsonRpc has been used, those arguments are part of the verification.
|
||||||
func (ctn UcanCtx) ExecutionAllowed() error {
|
func (ctn UcanCtx) ExecutionAllowed() error {
|
||||||
@@ -140,6 +156,13 @@ func (ctn UcanCtx) ExecutionAllowed() error {
|
|||||||
}
|
}
|
||||||
newArgs.Include(jsonRpcArgs)
|
newArgs.Include(jsonRpcArgs)
|
||||||
}
|
}
|
||||||
|
if ctn.infura != nil {
|
||||||
|
infuraArgs, err := ctn.infura.Args()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
newArgs.Include(infuraArgs)
|
||||||
|
}
|
||||||
|
|
||||||
return newArgs, nil
|
return newArgs, nil
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -107,15 +107,15 @@ func TestHttp(t *testing.T) {
|
|||||||
// we don't test the args hash here
|
// we don't test the args hash here
|
||||||
emptyArgs := args.New().ReadOnly()
|
emptyArgs := args.New().ReadOnly()
|
||||||
|
|
||||||
ctx := NewHttpExtArgs(pol, emptyArgs, r)
|
extArgs := NewHttpExtArgs(pol, emptyArgs, r)
|
||||||
|
|
||||||
_, err := ctx.Args()
|
_, err := extArgs.Args()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
if tc.expected {
|
if tc.expected {
|
||||||
require.NoError(t, ctx.Verify())
|
require.NoError(t, extArgs.Verify())
|
||||||
} else {
|
} 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)
|
err := invArgs.Add(HttpArgsKey, tc.hash)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
ctx := NewHttpExtArgs(pol, invArgs.ReadOnly(), req)
|
extArgs := NewHttpExtArgs(pol, invArgs.ReadOnly(), req)
|
||||||
|
|
||||||
if tc.expected {
|
if tc.expected {
|
||||||
require.NoError(t, ctx.Verify())
|
require.NoError(t, extArgs.Verify())
|
||||||
} else {
|
} 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
|
// we don't test the args hash here
|
||||||
emptyArgs := args.New().ReadOnly()
|
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)
|
require.NoError(t, err)
|
||||||
|
|
||||||
if tc.expected {
|
if tc.expected {
|
||||||
require.NoError(t, ctx.Verify())
|
require.NoError(t, extArgs.Verify())
|
||||||
} else {
|
} 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)
|
err := invArgs.Add(JsonRpcArgsKey, tc.hash)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
ctx := NewJsonRpcExtArgs(pol, invArgs.ReadOnly(), req)
|
extArgs := NewJsonRpcExtArgs(pol, invArgs.ReadOnly(), req)
|
||||||
|
|
||||||
if tc.expected {
|
if tc.expected {
|
||||||
require.NoError(t, ctx.Verify())
|
require.NoError(t, extArgs.Verify())
|
||||||
} else {
|
} else {
|
||||||
require.Error(t, ctx.Verify())
|
require.Error(t, extArgs.Verify())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user