Prepare stack middleware to handle IBC middleware
This commit is contained in:
parent
0ea2861311
commit
88781593bb
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue