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 {
app string
ibc bool
// this exposes the log.Logger and all other methods we don't override
naiveContext
}
@ -31,24 +32,35 @@ var _ basecoin.Context = secureContext{}
func (c secureContext) WithPermissions(perms ...basecoin.Actor) basecoin.Context {
// the guard makes sure you only set permissions for the app you are inside
for _, p := range perms {
// TODO: also check chainID, limit only certain middleware can set IBC?
if p.App != c.app {
err := errors.Errorf("Cannot set permission for %s from %s", c.app, p.App)
if !c.validPermisison(p) {
err := errors.Errorf("Cannot set permission for %s/%s on (app=%s, ibc=%b)",
p.ChainID, p.App, c.app, c.ibc)
panic(err)
}
}
return secureContext{
app: c.app,
ibc: c.ibc,
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,
// but carry on knowledge that this is a child
func (c secureContext) Reset() basecoin.Context {
return secureContext{
app: c.app,
ibc: c.ibc,
naiveContext: c.naiveContext.Reset().(naiveContext),
}
}
@ -71,6 +83,20 @@ func withApp(ctx basecoin.Context, app string) basecoin.Context {
}
return secureContext{
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,
}
}

View File

@ -12,6 +12,8 @@ import (
// heavily inspired by negroni's design
type middleware struct {
middleware Middleware
space string
allowIBC bool
next basecoin.Handler
}
@ -21,13 +23,20 @@ func (m *middleware) Name() string {
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
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
next := secureCheck(m.next, ctx)
// set the permissions for this app
ctx = withApp(ctx, m.Name())
store = stateSpace(store, m.Name())
ctx = m.wrapCtx(ctx)
store = stateSpace(store, m.space)
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
next := secureDeliver(m.next, ctx)
// set the permissions for this app
ctx = withApp(ctx, m.Name())
store = stateSpace(store, m.Name())
ctx = m.wrapCtx(ctx)
store = stateSpace(store, m.space)
return m.middleware.DeliverTx(ctx, store, tx, next)
}
func (m *middleware) SetOption(l log.Logger, store state.SimpleDB, module, key, value string) (string, error) {
// 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)
}
// 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
type Stack struct {
middles []Middleware
middles []builder
handler basecoin.Handler
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
// before you can execute it.
func New(middlewares ...Middleware) *Stack {
return &Stack{
middles: middlewares,
stack := new(Stack)
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
@ -77,10 +129,17 @@ func (s *Stack) Use(handler basecoin.Handler) *Stack {
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 {
return 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
grantee := basecoin.Actor{App: NameGrant, Address: []byte{1}}
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 {
asIBC bool
grant basecoin.Actor
require basecoin.Actor
expectedRes data.Bytes
expected func(error) bool
}{
{grantee, grantee, rawBytes, nil},
{grantee, grantee2, nil, errors.IsUnauthorizedErr},
{grantee, signer, nil, errors.IsUnauthorizedErr},
{signer, signer, nil, errors.IsInternalErr},
// grant as normal app middleware
{false, grantee, grantee, rawBytes, nil},
{false, grantee, grantee2, nil, errors.IsUnauthorizedErr},
{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 {
app := New(
Recovery{}, // we need this so panics turn to errors
GrantMiddleware{Auth: tc.grant},
CheckMiddleware{Required: tc.require},
).Use(EchoHandler{})
app := New(Recovery{})
if tc.asIBC {
app = app.IBC(GrantMiddleware{Auth: tc.grant})
} else {
app = app.Apps(GrantMiddleware{Auth: tc.grant})
}
app = app.
Apps(CheckMiddleware{Required: tc.require}).
Use(EchoHandler{})
res, err := app.CheckTx(ctx, store, raw)
checkPerm(t, i, tc.expectedRes, tc.expected, res, err)