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 {
|
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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue