Prepare stack middleware to handle IBC middleware

This commit is contained in:
Ethan Frey 2017-07-13 13:38:23 +02:00
parent 0ea2861311
commit 88781593bb
3 changed files with 129 additions and 23 deletions

View File

@ -14,6 +14,7 @@ type nonce int64
type secureContext struct { type secureContext struct {
app string app string
ibc bool
// this exposes the log.Logger and all other methods we don't override // this exposes the log.Logger and all other methods we don't override
naiveContext naiveContext
} }
@ -31,24 +32,35 @@ var _ basecoin.Context = secureContext{}
func (c secureContext) WithPermissions(perms ...basecoin.Actor) basecoin.Context { func (c secureContext) WithPermissions(perms ...basecoin.Actor) basecoin.Context {
// the guard makes sure you only set permissions for the app you are inside // the guard makes sure you only set permissions for the app you are inside
for _, p := range perms { for _, p := range perms {
// TODO: also check chainID, limit only certain middleware can set IBC? if !c.validPermisison(p) {
if p.App != c.app { err := errors.Errorf("Cannot set permission for %s/%s on (app=%s, ibc=%b)",
err := errors.Errorf("Cannot set permission for %s from %s", c.app, p.App) p.ChainID, p.App, c.app, c.ibc)
panic(err) panic(err)
} }
} }
return secureContext{ return secureContext{
app: c.app, app: c.app,
ibc: c.ibc,
naiveContext: c.naiveContext.WithPermissions(perms...).(naiveContext), naiveContext: c.naiveContext.WithPermissions(perms...).(naiveContext),
} }
} }
func (c secureContext) validPermisison(p basecoin.Actor) bool {
// if app is set, then it must match
if c.app != "" && c.app != p.App {
return false
}
// if ibc, chain must be set, otherwise it must not
return c.ibc == (p.ChainID != "")
}
// Reset should clear out all permissions, // Reset should clear out all permissions,
// but carry on knowledge that this is a child // but carry on knowledge that this is a child
func (c secureContext) Reset() basecoin.Context { func (c secureContext) Reset() basecoin.Context {
return secureContext{ return secureContext{
app: c.app, app: c.app,
ibc: c.ibc,
naiveContext: c.naiveContext.Reset().(naiveContext), naiveContext: c.naiveContext.Reset().(naiveContext),
} }
} }
@ -71,6 +83,20 @@ func withApp(ctx basecoin.Context, app string) basecoin.Context {
} }
return secureContext{ return secureContext{
app: app, app: app,
ibc: false,
naiveContext: sc.naiveContext,
}
}
// withIBC is a private method so we can securely allow IBC permissioning
func withIBC(ctx basecoin.Context) basecoin.Context {
sc, ok := ctx.(secureContext)
if !ok {
return ctx
}
return secureContext{
app: "",
ibc: true,
naiveContext: sc.naiveContext, naiveContext: sc.naiveContext,
} }
} }

View File

@ -12,6 +12,8 @@ import (
// heavily inspired by negroni's design // heavily inspired by negroni's design
type middleware struct { type middleware struct {
middleware Middleware middleware Middleware
space string
allowIBC bool
next basecoin.Handler next basecoin.Handler
} }
@ -21,13 +23,20 @@ func (m *middleware) Name() string {
return m.middleware.Name() return m.middleware.Name()
} }
func (m *middleware) wrapCtx(ctx basecoin.Context) basecoin.Context {
if m.allowIBC {
return withIBC(ctx)
}
return withApp(ctx, m.space)
}
// CheckTx always returns an empty success tx // CheckTx always returns an empty success tx
func (m *middleware) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (basecoin.Result, error) { func (m *middleware) CheckTx(ctx basecoin.Context, store state.SimpleDB, tx basecoin.Tx) (basecoin.Result, error) {
// make sure we pass in proper context to child // make sure we pass in proper context to child
next := secureCheck(m.next, ctx) next := secureCheck(m.next, ctx)
// set the permissions for this app // set the permissions for this app
ctx = withApp(ctx, m.Name()) ctx = m.wrapCtx(ctx)
store = stateSpace(store, m.Name()) store = stateSpace(store, m.space)
return m.middleware.CheckTx(ctx, store, tx, next) return m.middleware.CheckTx(ctx, store, tx, next)
} }
@ -37,22 +46,48 @@ func (m *middleware) DeliverTx(ctx basecoin.Context, store state.SimpleDB, tx ba
// make sure we pass in proper context to child // make sure we pass in proper context to child
next := secureDeliver(m.next, ctx) next := secureDeliver(m.next, ctx)
// set the permissions for this app // set the permissions for this app
ctx = withApp(ctx, m.Name()) ctx = m.wrapCtx(ctx)
store = stateSpace(store, m.Name()) store = stateSpace(store, m.space)
return m.middleware.DeliverTx(ctx, store, tx, next) return m.middleware.DeliverTx(ctx, store, tx, next)
} }
func (m *middleware) SetOption(l log.Logger, store state.SimpleDB, module, key, value string) (string, error) { func (m *middleware) SetOption(l log.Logger, store state.SimpleDB, module, key, value string) (string, error) {
// set the namespace for the app // set the namespace for the app
store = stateSpace(store, m.Name()) store = stateSpace(store, m.space)
return m.middleware.SetOption(l, store, module, key, value, m.next) return m.middleware.SetOption(l, store, module, key, value, m.next)
} }
// builder is used to associate info with the middleware, so we can build
// it properly
type builder struct {
middleware Middleware
stateSpace string
allowIBC bool
}
func prep(m Middleware, ibc bool) builder {
return builder{
middleware: m,
stateSpace: m.Name(),
allowIBC: ibc,
}
}
// wrap sets up the middleware with the proper options
func (b builder) wrap(next basecoin.Handler) basecoin.Handler {
return &middleware{
middleware: b.middleware,
space: b.stateSpace,
allowIBC: b.allowIBC,
next: next,
}
}
// Stack is the entire application stack // Stack is the entire application stack
type Stack struct { type Stack struct {
middles []Middleware middles []builder
handler basecoin.Handler handler basecoin.Handler
basecoin.Handler // the compiled version, which we expose basecoin.Handler // the compiled version, which we expose
} }
@ -62,9 +97,26 @@ var _ basecoin.Handler = &Stack{}
// New prepares a middleware stack, you must `.Use()` a Handler // New prepares a middleware stack, you must `.Use()` a Handler
// before you can execute it. // before you can execute it.
func New(middlewares ...Middleware) *Stack { func New(middlewares ...Middleware) *Stack {
return &Stack{ stack := new(Stack)
middles: middlewares, return stack.Apps(middlewares...)
}
// Apps adds the following Middlewares as typical application
// middleware to the stack (limit permission to one app)
func (s *Stack) Apps(middlewares ...Middleware) *Stack {
// TODO: some wrapper...
for _, m := range middlewares {
s.middles = append(s.middles, prep(m, false))
} }
return s
}
// IBC add the following middleware with permission to add cross-chain
// permissions
func (s *Stack) IBC(m Middleware) *Stack {
// TODO: some wrapper...
s.middles = append(s.middles, prep(m, true))
return s
} }
// Use sets the final handler for the stack and prepares it for use // Use sets the final handler for the stack and prepares it for use
@ -77,10 +129,17 @@ func (s *Stack) Use(handler basecoin.Handler) *Stack {
return s return s
} }
func build(mid []Middleware, end basecoin.Handler) basecoin.Handler { // Dispatch is like Use, but a convenience method to construct a
// dispatcher with a set of modules to route.
func (s *Stack) Dispatch(routes ...Dispatchable) *Stack {
d := NewDispatcher(routes...)
return s.Use(d)
}
func build(mid []builder, end basecoin.Handler) basecoin.Handler {
if len(mid) == 0 { if len(mid) == 0 {
return end return end
} }
next := build(mid[1:], end) next := build(mid[1:], end)
return &middleware{mid[0], next} return mid[0].wrap(next)
} }

View File

@ -31,25 +31,46 @@ func TestPermissionSandbox(t *testing.T) {
// test cases to make sure permissioning is solid // test cases to make sure permissioning is solid
grantee := basecoin.Actor{App: NameGrant, Address: []byte{1}} grantee := basecoin.Actor{App: NameGrant, Address: []byte{1}}
grantee2 := basecoin.Actor{App: NameGrant, Address: []byte{2}} grantee2 := basecoin.Actor{App: NameGrant, Address: []byte{2}}
signer := basecoin.Actor{App: nameSigner, Address: []byte{1}} // ibc and grantee are the same, just different chains
ibc := basecoin.Actor{ChainID: "other", App: NameGrant, Address: []byte{1}}
ibc2 := basecoin.Actor{ChainID: "other", App: nameSigner, Address: []byte{21}}
signer := basecoin.Actor{App: nameSigner, Address: []byte{21}}
cases := []struct { cases := []struct {
asIBC bool
grant basecoin.Actor grant basecoin.Actor
require basecoin.Actor require basecoin.Actor
expectedRes data.Bytes expectedRes data.Bytes
expected func(error) bool expected func(error) bool
}{ }{
{grantee, grantee, rawBytes, nil}, // grant as normal app middleware
{grantee, grantee2, nil, errors.IsUnauthorizedErr}, {false, grantee, grantee, rawBytes, nil},
{grantee, signer, nil, errors.IsUnauthorizedErr}, {false, grantee, grantee2, nil, errors.IsUnauthorizedErr},
{signer, signer, nil, errors.IsInternalErr}, {false, grantee2, grantee2, rawBytes, nil},
{false, ibc, grantee, nil, errors.IsInternalErr},
{false, grantee, ibc, nil, errors.IsUnauthorizedErr},
{false, grantee, signer, nil, errors.IsUnauthorizedErr},
{false, signer, signer, nil, errors.IsInternalErr},
// grant as ibc middleware
{true, ibc, ibc, rawBytes, nil}, // ibc can set permissions
{true, ibc2, ibc2, rawBytes, nil}, // for any app
// the must match, both app and chain
{true, ibc, ibc2, nil, errors.IsUnauthorizedErr},
{true, ibc, grantee, nil, errors.IsUnauthorizedErr},
// cannot set local apps from ibc middleware
{true, grantee, grantee, nil, errors.IsInternalErr},
} }
for i, tc := range cases { for i, tc := range cases {
app := New( app := New(Recovery{})
Recovery{}, // we need this so panics turn to errors if tc.asIBC {
GrantMiddleware{Auth: tc.grant}, app = app.IBC(GrantMiddleware{Auth: tc.grant})
CheckMiddleware{Required: tc.require}, } else {
).Use(EchoHandler{}) app = app.Apps(GrantMiddleware{Auth: tc.grant})
}
app = app.
Apps(CheckMiddleware{Required: tc.require}).
Use(EchoHandler{})
res, err := app.CheckTx(ctx, store, raw) res, err := app.CheckTx(ctx, store, raw)
checkPerm(t, i, tc.expectedRes, tc.expected, res, err) checkPerm(t, i, tc.expectedRes, tc.expected, res, err)