256 lines
8.1 KiB
Go
256 lines
8.1 KiB
Go
package keeper
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/gogo/protobuf/proto"
|
|
|
|
"github.com/tendermint/tendermint/libs/log"
|
|
|
|
"github.com/cosmos/cosmos-sdk/codec"
|
|
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
|
|
storetypes "github.com/cosmos/cosmos-sdk/store/types"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
|
"github.com/cosmos/cosmos-sdk/x/auth/middleware"
|
|
"github.com/cosmos/cosmos-sdk/x/authz"
|
|
)
|
|
|
|
type Keeper struct {
|
|
storeKey storetypes.StoreKey
|
|
cdc codec.BinaryCodec
|
|
router *middleware.MsgServiceRouter
|
|
}
|
|
|
|
// NewKeeper constructs a message authorization Keeper
|
|
func NewKeeper(storeKey storetypes.StoreKey, cdc codec.BinaryCodec, router *middleware.MsgServiceRouter) Keeper {
|
|
return Keeper{
|
|
storeKey: storeKey,
|
|
cdc: cdc,
|
|
router: router,
|
|
}
|
|
}
|
|
|
|
// Logger returns a module-specific logger.
|
|
func (k Keeper) Logger(ctx sdk.Context) log.Logger {
|
|
return ctx.Logger().With("module", fmt.Sprintf("x/%s", authz.ModuleName))
|
|
}
|
|
|
|
// getGrant returns grant stored at skey.
|
|
func (k Keeper) getGrant(ctx sdk.Context, skey []byte) (grant authz.Grant, found bool) {
|
|
store := ctx.KVStore(k.storeKey)
|
|
bz := store.Get(skey)
|
|
if bz == nil {
|
|
return grant, false
|
|
}
|
|
k.cdc.MustUnmarshal(bz, &grant)
|
|
return grant, true
|
|
}
|
|
|
|
func (k Keeper) update(ctx sdk.Context, grantee sdk.AccAddress, granter sdk.AccAddress, updated authz.Authorization) error {
|
|
skey := grantStoreKey(grantee, granter, updated.MsgTypeURL())
|
|
grant, found := k.getGrant(ctx, skey)
|
|
if !found {
|
|
return sdkerrors.ErrNotFound.Wrap("authorization not found")
|
|
}
|
|
|
|
msg, ok := updated.(proto.Message)
|
|
if !ok {
|
|
sdkerrors.ErrPackAny.Wrapf("cannot proto marshal %T", updated)
|
|
}
|
|
|
|
any, err := codectypes.NewAnyWithValue(msg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
grant.Authorization = any
|
|
store := ctx.KVStore(k.storeKey)
|
|
store.Set(skey, k.cdc.MustMarshal(&grant))
|
|
return nil
|
|
}
|
|
|
|
// DispatchActions attempts to execute the provided messages via authorization
|
|
// grants from the message signer to the grantee.
|
|
func (k Keeper) DispatchActions(ctx sdk.Context, grantee sdk.AccAddress, msgs []sdk.Msg) ([][]byte, error) {
|
|
var results = make([][]byte, len(msgs))
|
|
for i, msg := range msgs {
|
|
signers := msg.GetSigners()
|
|
if len(signers) != 1 {
|
|
return nil, sdkerrors.ErrInvalidRequest.Wrap("authorization can be given to msg with only one signer")
|
|
}
|
|
granter := signers[0]
|
|
|
|
// if granter != grantee then check authorization.Accept, otherwise we implicitly accept.
|
|
if !granter.Equals(grantee) {
|
|
authorization, _ := k.GetCleanAuthorization(ctx, grantee, granter, sdk.MsgTypeURL(msg))
|
|
if authorization == nil {
|
|
return nil, sdkerrors.ErrUnauthorized.Wrap("authorization not found")
|
|
}
|
|
resp, err := authorization.Accept(ctx, msg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if resp.Delete {
|
|
err = k.DeleteGrant(ctx, grantee, granter, sdk.MsgTypeURL(msg))
|
|
} else if resp.Updated != nil {
|
|
err = k.update(ctx, grantee, granter, resp.Updated)
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !resp.Accept {
|
|
return nil, sdkerrors.ErrUnauthorized
|
|
}
|
|
}
|
|
|
|
handler := k.router.Handler(msg)
|
|
if handler == nil {
|
|
return nil, sdkerrors.ErrUnknownRequest.Wrapf("unrecognized message route: %s", sdk.MsgTypeURL(msg))
|
|
}
|
|
|
|
msgResp, err := handler(ctx, msg)
|
|
if err != nil {
|
|
return nil, sdkerrors.Wrapf(err, "failed to execute message; message %v", msg)
|
|
}
|
|
results[i] = msgResp.Data
|
|
|
|
// emit the events from the dispatched actions
|
|
events := msgResp.Events
|
|
sdkEvents := make([]sdk.Event, 0, len(events))
|
|
for i := 0; i < len(events); i++ {
|
|
sdkEvents = append(sdkEvents, sdk.Event(events[i]))
|
|
}
|
|
ctx.EventManager().EmitEvents(sdkEvents)
|
|
}
|
|
|
|
return results, nil
|
|
}
|
|
|
|
// SaveGrant method grants the provided authorization to the grantee on the granter's account
|
|
// with the provided expiration time. If there is an existing authorization grant for the
|
|
// same `sdk.Msg` type, this grant overwrites that.
|
|
func (k Keeper) SaveGrant(ctx sdk.Context, grantee, granter sdk.AccAddress, authorization authz.Authorization, expiration time.Time) error {
|
|
store := ctx.KVStore(k.storeKey)
|
|
|
|
grant, err := authz.NewGrant(authorization, expiration)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
bz := k.cdc.MustMarshal(&grant)
|
|
skey := grantStoreKey(grantee, granter, authorization.MsgTypeURL())
|
|
store.Set(skey, bz)
|
|
return ctx.EventManager().EmitTypedEvent(&authz.EventGrant{
|
|
MsgTypeUrl: authorization.MsgTypeURL(),
|
|
Granter: granter.String(),
|
|
Grantee: grantee.String(),
|
|
})
|
|
}
|
|
|
|
// DeleteGrant revokes any authorization for the provided message type granted to the grantee
|
|
// by the granter.
|
|
func (k Keeper) DeleteGrant(ctx sdk.Context, grantee sdk.AccAddress, granter sdk.AccAddress, msgType string) error {
|
|
store := ctx.KVStore(k.storeKey)
|
|
skey := grantStoreKey(grantee, granter, msgType)
|
|
_, found := k.getGrant(ctx, skey)
|
|
if !found {
|
|
return sdkerrors.ErrNotFound.Wrap("authorization not found")
|
|
}
|
|
store.Delete(skey)
|
|
return ctx.EventManager().EmitTypedEvent(&authz.EventRevoke{
|
|
MsgTypeUrl: msgType,
|
|
Granter: granter.String(),
|
|
Grantee: grantee.String(),
|
|
})
|
|
}
|
|
|
|
// GetAuthorizations Returns list of `Authorizations` granted to the grantee by the granter.
|
|
func (k Keeper) GetAuthorizations(ctx sdk.Context, grantee sdk.AccAddress, granter sdk.AccAddress) (authorizations []authz.Authorization) {
|
|
store := ctx.KVStore(k.storeKey)
|
|
key := grantStoreKey(grantee, granter, "")
|
|
iter := sdk.KVStorePrefixIterator(store, key)
|
|
defer iter.Close()
|
|
var authorization authz.Grant
|
|
for ; iter.Valid(); iter.Next() {
|
|
k.cdc.MustUnmarshal(iter.Value(), &authorization)
|
|
authorizations = append(authorizations, authorization.GetAuthorization())
|
|
}
|
|
return authorizations
|
|
}
|
|
|
|
// GetCleanAuthorization returns an `Authorization` and it's expiration time for
|
|
// (grantee, granter, message name) grant. If there is no grant `nil` is returned.
|
|
// If the grant is expired, the grant is revoked, removed from the storage, and `nil` is returned.
|
|
func (k Keeper) GetCleanAuthorization(ctx sdk.Context, grantee sdk.AccAddress, granter sdk.AccAddress, msgType string) (cap authz.Authorization, expiration time.Time) {
|
|
grant, found := k.getGrant(ctx, grantStoreKey(grantee, granter, msgType))
|
|
if !found {
|
|
return nil, time.Time{}
|
|
}
|
|
if grant.Expiration.Before(ctx.BlockHeader().Time) {
|
|
k.DeleteGrant(ctx, grantee, granter, msgType)
|
|
return nil, time.Time{}
|
|
}
|
|
|
|
return grant.GetAuthorization(), grant.Expiration
|
|
}
|
|
|
|
// IterateGrants iterates over all authorization grants
|
|
// This function should be used with caution because it can involve significant IO operations.
|
|
// It should not be used in query or msg services without charging additional gas.
|
|
func (k Keeper) IterateGrants(ctx sdk.Context,
|
|
handler func(granterAddr sdk.AccAddress, granteeAddr sdk.AccAddress, grant authz.Grant) bool) {
|
|
store := ctx.KVStore(k.storeKey)
|
|
iter := sdk.KVStorePrefixIterator(store, GrantKey)
|
|
defer iter.Close()
|
|
for ; iter.Valid(); iter.Next() {
|
|
var grant authz.Grant
|
|
granterAddr, granteeAddr := addressesFromGrantStoreKey(iter.Key())
|
|
k.cdc.MustUnmarshal(iter.Value(), &grant)
|
|
if handler(granterAddr, granteeAddr, grant) {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// ExportGenesis returns a GenesisState for a given context.
|
|
func (k Keeper) ExportGenesis(ctx sdk.Context) *authz.GenesisState {
|
|
var entries []authz.GrantAuthorization
|
|
k.IterateGrants(ctx, func(granter, grantee sdk.AccAddress, grant authz.Grant) bool {
|
|
exp := grant.Expiration
|
|
entries = append(entries, authz.GrantAuthorization{
|
|
Granter: granter.String(),
|
|
Grantee: grantee.String(),
|
|
Expiration: exp,
|
|
Authorization: grant.Authorization,
|
|
})
|
|
return false
|
|
})
|
|
|
|
return authz.NewGenesisState(entries)
|
|
}
|
|
|
|
// InitGenesis new authz genesis
|
|
func (k Keeper) InitGenesis(ctx sdk.Context, data *authz.GenesisState) {
|
|
for _, entry := range data.Authorization {
|
|
grantee, err := sdk.AccAddressFromBech32(entry.Grantee)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
granter, err := sdk.AccAddressFromBech32(entry.Granter)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
a, ok := entry.Authorization.GetCachedValue().(authz.Authorization)
|
|
if !ok {
|
|
panic("expected authorization")
|
|
}
|
|
|
|
err = k.SaveGrant(ctx, grantee, granter, a, entry.Expiration)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
}
|