package exectx import ( "errors" "fmt" "net/http" "slices" "github.com/INFURA/go-ethlibs/jsonrpc" "github.com/ipfs/go-cid" "github.com/ucan-wg/go-ucan/pkg/args" "github.com/ucan-wg/go-ucan/pkg/command" "github.com/ucan-wg/go-ucan/pkg/container" "github.com/ucan-wg/go-ucan/pkg/meta" "github.com/ucan-wg/go-ucan/pkg/policy" "github.com/ucan-wg/go-ucan/token/delegation" "github.com/ucan-wg/go-ucan/token/invocation" bearer2 "github.com/INFURA/go-ucan-toolkit/server/bearer" ) var _ delegation.Loader = UcanCtx{} // UcanCtx is a UCAN execution context meant to be inserted in the go context while handling a request. // It allows to handle the control of the execution in multiple steps across different middlewares, // as well as doing "bearer" types of controls, when arguments are derived from the request itself (HTTP, JsonRpc). type UcanCtx struct { inv *invocation.Token dlgs map[cid.Cid]*delegation.Token policies policy.Policy // all policies combined meta *meta.Meta // all meta combined, with no overwriting // argument sources http *bearer2.HttpBearer jsonrpc *bearer2.JsonRpcBearer } func FromContainer(cont container.Reader) (*UcanCtx, error) { inv, err := cont.GetInvocation() if err != nil { return nil, fmt.Errorf("no invocation: %w", err) } ctx := &UcanCtx{ inv: inv, dlgs: make(map[cid.Cid]*delegation.Token, len(cont)-1), meta: meta.NewMeta(), } // iterate in reverse, from the root delegation to the leaf for _, c := range slices.Backward(inv.Proof()) { // make sure we have the delegation dlg, err := cont.GetDelegation(c) if errors.Is(err, delegation.ErrDelegationNotFound) { return nil, fmt.Errorf("delegation proof %s is missing", c) } if err != nil { return nil, err } ctx.dlgs[c] = dlg // accumulate the policies ctx.policies = append(ctx.policies, dlg.Policy()...) // accumulate the meta values, with no overwriting ctx.meta.Include(dlg.Meta()) } return ctx, nil } // Command returns the command triggered by the invocation. func (ctn UcanCtx) Command() command.Command { return ctn.inv.Command() } // Invocation returns the invocation.Token. func (ctn UcanCtx) Invocation() *invocation.Token { return ctn.inv } // GetDelegation returns the delegation.Token matching the given CID. // If not found, delegation.ErrDelegationNotFound is returned. func (ctn UcanCtx) GetDelegation(cid cid.Cid) (*delegation.Token, error) { if dlg, ok := ctn.dlgs[cid]; ok { return dlg, nil } return nil, delegation.ErrDelegationNotFound } // Policies return the full set of policy statements to satisfy. func (ctn UcanCtx) Policies() policy.Policy { return ctn.policies } // Meta returns all the meta values from the delegations. // They are accumulated from the root delegation to the leaf delegation, with no overwrite. func (ctn UcanCtx) Meta() meta.ReadOnly { return ctn.meta.ReadOnly() } // VerifyHttp verify the delegation's policies against arguments constructed from the HTTP request. // 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 { if ctn.http == nil { panic("only use once per request context") } ctn.http = bearer2.NewHttpBearer(ctn.policies, ctn.inv.Arguments(), req) return ctn.http.Verify() } // VerifyJsonRpc verify the delegation's policies against arguments constructed from the JsonRpc request. // 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 { if ctn.jsonrpc != nil { panic("only use once per request context") } ctn.jsonrpc = bearer2.NewJsonRpcBearer(ctn.policies, ctn.inv.Arguments(), req) return ctn.jsonrpc.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 { return ctn.inv.ExecutionAllowedWithArgsHook(ctn, func(args args.ReadOnly) (*args.Args, error) { newArgs := args.WriteableClone() if ctn.http != nil { httpArgs, err := ctn.http.Args() if err != nil { return nil, err } newArgs.Include(httpArgs) } if ctn.jsonrpc != nil { jsonRpcArgs, err := ctn.jsonrpc.Args() if err != nil { return nil, err } newArgs.Include(jsonRpcArgs) } return newArgs, nil }) }