Middleware system with secure auth contexts
This commit is contained in:
parent
f6395c8981
commit
98e65124a1
22
context.go
Normal file
22
context.go
Normal file
@ -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
|
||||
}
|
||||
62
handler.go
62
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 {
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
|
||||
104
handlers/context.go
Normal file
104
handlers/context.go
Normal file
@ -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)
|
||||
}
|
||||
73
handlers/middleware.go
Normal file
73
handlers/middleware.go
Normal file
@ -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}
|
||||
}
|
||||
@ -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) {
|
||||
|
||||
29
handlers/util.go
Normal file
29
handlers/util.go
Normal file
@ -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
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user