diff --git a/context.go b/context.go new file mode 100644 index 00000000000..21b91deb850 --- /dev/null +++ b/context.go @@ -0,0 +1,22 @@ +package basecoin + +import "github.com/tendermint/go-wire/data" + +type Permission struct { + App string // Which app authorized this? + Address data.Bytes // App-specific identifier +} + +func NewPermission(app string, addr []byte) Permission { + return Permission{App: app, Address: addr} +} + +// Context is an interface, so we can implement "secure" variants that +// rely on private fields to control the actions +type Context interface { + // context.Context + WithPermissions(perms ...Permission) Context + HasPermission(perm Permission) bool + IsParent(ctx Context) bool + Reset() Context +} diff --git a/handler.go b/handler.go index acebaa49813..14bb31a79bc 100644 --- a/handler.go +++ b/handler.go @@ -1,8 +1,6 @@ package basecoin import ( - "bytes" - abci "github.com/tendermint/abci/types" "github.com/tendermint/go-wire/data" @@ -17,18 +15,36 @@ type Checker interface { CheckTx(ctx Context, store types.KVStore, tx Tx) (Result, error) } -type Deliver interface { - DeliverTx(ctx Context, store types.KVStore, tx Tx) (Result, error) -} - type CheckerMiddle interface { CheckTx(ctx Context, store types.KVStore, tx Tx, next Checker) (Result, error) } +// CheckerFunc (like http.HandlerFunc) is a shortcut for making wrapers +type CheckerFunc func(Context, types.KVStore, Tx) (Result, error) + +func (c CheckerFunc) CheckTx(ctx Context, store types.KVStore, tx Tx) (Result, error) { + return c(ctx, store, tx) +} + +var _ Checker = CheckerFunc(nil) + +type Deliver interface { + DeliverTx(ctx Context, store types.KVStore, tx Tx) (Result, error) +} + type DeliverMiddle interface { DeliverTx(ctx Context, store types.KVStore, tx Tx, next Deliver) (Result, error) } +// DeliverFunc (like http.HandlerFunc) is a shortcut for making wrapers +type DeliverFunc func(Context, types.KVStore, Tx) (Result, error) + +func (c DeliverFunc) DeliverTx(ctx Context, store types.KVStore, tx Tx) (Result, error) { + return c(ctx, store, tx) +} + +var _ Deliver = DeliverFunc(nil) + // Handler is anything that processes a transaction type Handler interface { Checker @@ -51,40 +67,6 @@ type Middleware interface { Named } -// TODO: handle this in some secure way, only certain apps can add permissions -type Permission struct { - App string // Which app authorized this? - Address []byte // App-specific identifier -} - -// TODO: Context is a place-holder, soon we add some request data here from the -// higher-levels (like tell an app who signed). -// Trust me, we will need it like CallContext now... -type Context struct { - perms []Permission -} - -// TOTALLY insecure. will redo later, but you get the point -func (c Context) AddPermissions(perms ...Permission) Context { - return Context{ - perms: append(c.perms, perms...), - } -} - -func (c Context) HasPermission(app string, addr []byte) bool { - for _, p := range c.perms { - if app == p.App && bytes.Equal(addr, p.Address) { - return true - } - } - return false -} - -// New should give a fresh context, and know what info makes sense to carry over -func (c Context) New() Context { - return Context{} -} - // Result captures any non-error abci result // to make sure people use error for error cases type Result struct { diff --git a/handlers/base.go b/handlers/base.go index 3df527d5bf6..3b4c6b4be7f 100644 --- a/handlers/base.go +++ b/handlers/base.go @@ -44,7 +44,7 @@ func (h SimpleFeeHandler) CheckTx(ctx basecoin.Context, store types.KVStore, tx return res, errors.InsufficientFees() } - if !ctx.HasPermission(NameSigs, feeTx.Payer) { + if !ctx.HasPermission(SigPerm(feeTx.Payer)) { return res, errors.Unauthorized() } @@ -67,7 +67,7 @@ func (h SimpleFeeHandler) DeliverTx(ctx basecoin.Context, store types.KVStore, t return res, errors.InsufficientFees() } - if !ctx.HasPermission(NameSigs, feeTx.Payer) { + if !ctx.HasPermission(SigPerm(feeTx.Payer)) { return res, errors.Unauthorized() } diff --git a/handlers/context.go b/handlers/context.go new file mode 100644 index 00000000000..baf750d09ea --- /dev/null +++ b/handlers/context.go @@ -0,0 +1,104 @@ +package handlers + +import ( + "bytes" + "math/rand" + + "github.com/pkg/errors" + "github.com/tendermint/basecoin" + "github.com/tendermint/basecoin/types" +) + +// store nonce as it's own type so no one can even try to fake it +type nonce int64 + +type secureContext struct { + id nonce + app string + perms []basecoin.Permission +} + +func NewContext() basecoin.Context { + return secureContext{ + id: nonce(rand.Int63()), + } +} + +var _ basecoin.Context = secureContext{} + +// WithPermissions will panic if they try to set permission without the proper app +func (c secureContext) WithPermissions(perms ...basecoin.Permission) basecoin.Context { + // the guard makes sure you only set permissions for the app you are inside + for _, p := range perms { + if p.App != c.app { + err := errors.Errorf("Cannot set permission for %s from %s", c.app, p.App) + panic(err) + } + } + + return secureContext{ + id: c.id, + perms: append(c.perms, perms...), + } +} + +func (c secureContext) HasPermission(perm basecoin.Permission) bool { + for _, p := range c.perms { + if perm.App == p.App && bytes.Equal(perm.Address, p.Address) { + return true + } + } + return false +} + +// IsParent ensures that this is derived from the given secureClient +func (c secureContext) IsParent(other basecoin.Context) bool { + so, ok := other.(secureContext) + if !ok { + return false + } + return c.id == so.id +} + +// Reset should give a fresh context, +// but carry on knowledge that this is a child +func (c secureContext) Reset() basecoin.Context { + return secureContext{ + id: c.id, + app: c.app, + } +} + +// withApp is a private method that we can use to properly set the +// app controls in the middleware +func withApp(ctx basecoin.Context, app string) basecoin.Context { + sc, ok := ctx.(secureContext) + if !ok { + return ctx + } + return secureContext{ + id: sc.id, + app: app, + perms: sc.perms, + } +} + +func secureCheck(h basecoin.Checker, parent basecoin.Context) basecoin.Checker { + next := func(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) { + if !parent.IsParent(ctx) { + return res, errors.New("Passing in non-child Context") + } + return h.CheckTx(ctx, store, tx) + } + return basecoin.CheckerFunc(next) +} + +func secureDeliver(h basecoin.Deliver, parent basecoin.Context) basecoin.Deliver { + next := func(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) { + if !parent.IsParent(ctx) { + return res, errors.New("Passing in non-child Context") + } + return h.DeliverTx(ctx, store, tx) + } + return basecoin.DeliverFunc(next) +} diff --git a/handlers/middleware.go b/handlers/middleware.go new file mode 100644 index 00000000000..bf46efd396a --- /dev/null +++ b/handlers/middleware.go @@ -0,0 +1,73 @@ +package handlers + +import ( + "github.com/tendermint/basecoin" + "github.com/tendermint/basecoin/types" +) + +// middleware lets us wrap a whole stack up into one Handler +// +// heavily inspired by negroni's design +type middleware struct { + middleware basecoin.Middleware + next basecoin.Handler +} + +var _ basecoin.Handler = &middleware{} + +func (m *middleware) Name() string { + return m.middleware.Name() +} + +// CheckTx always returns an empty success tx +func (m *middleware) CheckTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx) (basecoin.Result, error) { + // make sure we pass in proper context to child + next := secureCheck(m.next, ctx) + // set the permissions for this app + ctx = withApp(ctx, m.Name()) + return m.middleware.CheckTx(ctx, store, tx, next) +} + +// DeliverTx always returns an empty success tx +func (m *middleware) DeliverTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) { + // make sure we pass in proper context to child + next := secureDeliver(m.next, ctx) + // set the permissions for this app + ctx = withApp(ctx, m.Name()) + return m.middleware.DeliverTx(ctx, store, tx, next) +} + +// Stack is the entire application stack +type Stack struct { + middles []basecoin.Middleware + handler basecoin.Handler + basecoin.Handler // the compiled version, which we expose +} + +var _ basecoin.Handler = &Stack{} + +// NewStack prepares a middleware stack, you must `.Use()` a Handler +// before you can execute it. +func NewStack(middlewares ...basecoin.Middleware) *Stack { + return &Stack{ + middles: middlewares, + } +} + +// Use sets the final handler for the stack and prepares it for use +func (s *Stack) Use(handler basecoin.Handler) *Stack { + if handler == nil { + panic("Cannot have a Stack without an end handler") + } + s.handler = handler + s.Handler = build(s.middles, s.handler) + return s +} + +func build(mid []basecoin.Middleware, end basecoin.Handler) basecoin.Handler { + if len(mid) == 0 { + return end + } + next := build(mid[1:], end) + return &middleware{mid[0], next} +} diff --git a/handlers/sigs.go b/handlers/sigs.go index 05d323319c5..eb8136680d4 100644 --- a/handlers/sigs.go +++ b/handlers/sigs.go @@ -23,6 +23,10 @@ func (_ SignedHandler) Name() string { var _ basecoin.Middleware = SignedHandler{} +func SigPerm(addr []byte) basecoin.Permission { + return basecoin.Permission{App: NameSigs, Address: addr} +} + // Signed allows us to use txs.OneSig and txs.MultiSig (and others??) type Signed interface { basecoin.TxLayer @@ -50,10 +54,10 @@ func (h SignedHandler) DeliverTx(ctx basecoin.Context, store types.KVStore, tx b func addSigners(ctx basecoin.Context, sigs []crypto.PubKey) basecoin.Context { perms := make([]basecoin.Permission, len(sigs)) for i, s := range sigs { - perms[i] = basecoin.Permission{App: NameSigs, Address: s.Address()} + perms[i] = SigPerm(s.Address()) } // add the signers to the context and continue - return ctx.AddPermissions(perms...) + return ctx.WithPermissions(perms...) } func getSigners(tx basecoin.Tx) ([]crypto.PubKey, basecoin.Tx, error) { diff --git a/handlers/util.go b/handlers/util.go new file mode 100644 index 00000000000..51089257a3a --- /dev/null +++ b/handlers/util.go @@ -0,0 +1,29 @@ +package handlers + +import ( + "github.com/tendermint/basecoin" + "github.com/tendermint/basecoin/types" +) + +const ( + NameVoid = "void" +) + +// voidHandler just used to return okay to everything +type voidHandler struct{} + +var _ basecoin.Handler = voidHandler{} + +func (_ voidHandler) Name() string { + return NameVoid +} + +// CheckTx always returns an empty success tx +func (_ voidHandler) CheckTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) { + return +} + +// DeliverTx always returns an empty success tx +func (_ voidHandler) DeliverTx(ctx basecoin.Context, store types.KVStore, tx basecoin.Tx) (res basecoin.Result, err error) { + return +}