Skip to content

Commit

Permalink
working POC
Browse files Browse the repository at this point in the history
  • Loading branch information
atheeshp committed May 15, 2024
1 parent 24b70aa commit 7c4fab8
Show file tree
Hide file tree
Showing 11 changed files with 370 additions and 158 deletions.
200 changes: 138 additions & 62 deletions api/cosmos/authz/v1beta1/tx.pulsar.go

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions proto/cosmos/authz/v1beta1/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ message MsgGrant {
string grantee = 2 [(cosmos_proto.scalar) = "cosmos.AddressString"];

cosmos.authz.v1beta1.Grant grant = 3 [(gogoproto.nullable) = false, (amino.dont_omitempty) = true];

// rules are conditions to execute the grant.
bytes rules = 4;
}

// MsgGrantResponse defines the Msg/MsgGrant response type.
Expand Down
2 changes: 1 addition & 1 deletion simapp/ante.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) {
ante.NewValidateMemoDecorator(options.AccountKeeper),
ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper),
ante.NewDeductFeeDecorator(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper, options.TxFeeChecker),
ante.NewAuthzDecorator(options.AuthzKeeper),
ante.NewAuthzDecorator(options.AuthzKeeper, options.AccountKeeper),
ante.NewSetPubKeyDecorator(options.AccountKeeper), // SetPubKeyDecorator must be called before all signature verification decorators
ante.NewValidateSigCountDecorator(options.AccountKeeper),
ante.NewSigGasConsumeDecorator(options.AccountKeeper, options.SigGasConsumer),
Expand Down
104 changes: 49 additions & 55 deletions x/auth/ante/authz_rules_ante.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package ante

import (
"fmt"
"strings"

errorsmod "cosmossdk.io/errors"
Expand All @@ -12,13 +11,20 @@ import (
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
)

const (
AllowedRecipients = "allowed_recipients"
MaxAmount = "max_amount"
)

type AuthzDecorator struct {
azk AuthzKeeper
ak AccountKeeper
}

func NewAuthzDecorator(azk AuthzKeeper) AuthzDecorator {
func NewAuthzDecorator(azk AuthzKeeper, ak AccountKeeper) AuthzDecorator {
return AuthzDecorator{
azk: azk,
ak: ak,
}
}

Expand All @@ -41,11 +47,6 @@ func (azd AuthzDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool,
// Check if the message is an authorization message
if authzMsg, ok := msg.(*authztypes.MsgExec); ok {

rulesKeys, err := azd.azk.GetAuthzRulesKeys(ctx)
if err != nil {
return ctx, err
}

msgs, err := authzMsg.GetMessages()
if err != nil {
return ctx, err
Expand All @@ -54,24 +55,10 @@ func (azd AuthzDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool,
for _, innerMsg := range msgs {
switch innerMsgConverted := innerMsg.(type) {

Check failure on line 56 in x/auth/ante/authz_rules_ante.go

View workflow job for this annotation

GitHub Actions / Analyze

singleCaseSwitch: should rewrite switch statement to if statement (gocritic)

Check failure on line 56 in x/auth/ante/authz_rules_ante.go

View workflow job for this annotation

GitHub Actions / Analyze

singleCaseSwitch: should rewrite switch statement to if statement (gocritic)
case *banktypes.MsgSend:
sendRuleKeysInterface, ok := rulesKeys["Send"]
if !ok {
fmt.Println("no rule keys")
continue
}

granter, err := azd.azk.AddressCodec().StringToBytes(innerMsgConverted.FromAddress)
if err != nil {
isRulesBroken, err := azd.handleSendAuthzRules(ctx, innerMsgConverted, grantee)
if isRulesBroken {
return ctx, err
}

_, rules := azd.azk.GetAuthzWithRules(ctx, grantee, granter, sdk.MsgTypeURL(&banktypes.MsgSend{}))
if rules != nil {
sendRulesKeys := sendRuleKeysInterface.([]string)
if checkSendAuthzRulesViolated(innerMsgConverted, rules, sendRulesKeys) {
return ctx, fmt.Errorf("authz rules are not meeting")
}
}
}
}
}
Expand All @@ -81,49 +68,56 @@ func (azd AuthzDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool,
return next(ctx, tx, simulate)
}

// checkSendAuthzRulesViolated returns true if the rules are voilated
func checkSendAuthzRulesViolated(msg *banktypes.MsgSend, sendAuthzRules map[string]interface{}, sendRulesKeys []string) bool {
for _, key := range sendRulesKeys {

fmt.Printf("\">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\": %v\n", ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")
fmt.Printf("sendAuthzRules: %v\n", sendAuthzRules)
if blockedAddrsStrInt, ok := sendAuthzRules["AllowRecipients"]; key == "AllowRecipients" && ok {
blockedAddrsStr := blockedAddrsStrInt.(string)
blockedAddrs := strings.Split(blockedAddrsStr, ",")
for _, blockedRecipient := range blockedAddrs {
if msg.ToAddress == blockedRecipient {
return true
// handleCheckSendAuthzRules returns true if the rules are voilated
func (azd AuthzDecorator) handleSendAuthzRules(ctx sdk.Context, msg *banktypes.MsgSend, grantee []byte) (bool, error) {

granter, err := azd.ak.AddressCodec().StringToBytes(msg.FromAddress)
if err != nil {
return true, err
}

_, rules := azd.azk.GetAuthzWithRules(ctx, grantee, granter, sdk.MsgTypeURL(&banktypes.MsgSend{}))
if rules != nil {
if allowedAddrs, ok := rules[AllowedRecipients]; ok {
allowedAddrsValue := allowedAddrs.(string)
allowedAddrs := strings.Split(allowedAddrsValue, ",")
isAllowed := false
for _, allowedRecipient := range allowedAddrs {
if msg.ToAddress == allowedRecipient {
isAllowed = true
break
}
}

if !isAllowed {
return true, errorsmod.Wrap(sdkerrors.ErrTxDecode, "Recipient is not in the allowed list of the grant")
}
}

if spendLimitInt, ok := sendAuthzRules["SpendLImit"]; key == "SpendLImit" && ok {
spendLimit := spendLimitInt.(string)
if spendLimitInterface, ok := rules[MaxAmount]; ok {
spendLimit := spendLimitInterface.(string)
limit, err := sdk.ParseCoinsNormalized(spendLimit)
if err != nil {
return true
return true, err
}
if !limit.IsAllGTE(msg.Amount) {
return true
return true, errorsmod.Wrap(sdkerrors.ErrTxDecode, "Amount exceeds the max_amount limit set by the granter")
}

return true
}

}

return false
return false, nil
}

func checkGenericAuthzRules(_ *authztypes.MsgGrant, authz *authztypes.GenericAuthorization, genericRules map[string]string) bool {
if msgsStr, ok := genericRules["blockedMessages"]; ok {
msgs := strings.Split(msgsStr, ",")
for _, v := range msgs {
if v == authz.Msg {
return true
}
}
}

return false
}
// func checkGenericAuthzRules(_ *authztypes.MsgGrant, authz *authztypes.GenericAuthorization, genericRules map[string]string) bool {
// if msgsStr, ok := genericRules["blockedMessages"]; ok {
// msgs := strings.Split(msgsStr, ",")
// for _, v := range msgs {
// if v == authz.Msg {
// return true
// }
// }
// }

// return false
// }
1 change: 0 additions & 1 deletion x/auth/ante/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,4 @@ type AuthzKeeper interface {
GetAuthzOptions() map[string]map[string]string
GetAuthzRulesKeys(ctx context.Context) (map[string]interface{}, error)
GetAuthzWithRules(ctx context.Context, grantee, granter sdk.AccAddress, msgType string) (authz.Authorization, map[string]interface{})
AddressCodec() address.Codec
}
20 changes: 20 additions & 0 deletions x/authz/authorization_grant.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,26 @@ func NewGrant(blockTime time.Time, a Authorization, expiration *time.Time) (Gran
}, nil
}

// NewGrantWithRules does the same as NewGrant but takes rules as extra arg.
func NewGrantWithRules(blockTime time.Time, a Authorization, expiration *time.Time, rules []byte) (Grant, error) {
if expiration != nil && !expiration.After(blockTime) {
return Grant{}, errorsmod.Wrapf(ErrInvalidExpirationTime, "expiration must be after the current block time (%v), got %v", blockTime.Format(time.RFC3339), expiration.Format(time.RFC3339))
}
msg, ok := a.(proto.Message)
if !ok {
return Grant{}, sdkerrors.ErrPackAny.Wrapf("cannot proto marshal %T", a)
}
any, err := cdctypes.NewAnyWithValue(msg)
if err != nil {
return Grant{}, err
}
return Grant{
Expiration: expiration,
Authorization: any,
Rules: rules,
}, nil
}

var _ cdctypes.UnpackInterfacesMessage = &Grant{}

// UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces
Expand Down
17 changes: 17 additions & 0 deletions x/authz/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cli
import (
"errors"
"fmt"
"os"
"strings"
"time"

Expand Down Expand Up @@ -32,6 +33,7 @@ const (
delegate = "delegate"
redelegate = "redelegate"
unbond = "unbond"
FlagAuthzRules = "authz-rules"
)

// GetTxCmd returns the transaction commands for this module
Expand Down Expand Up @@ -203,6 +205,20 @@ Examples:
return err
}

rules, err := cmd.Flags().GetString(FlagAuthzRules)
if err != nil {
return err
}

if rules != "" {
contents, err := os.ReadFile(rules)

Check failure

Code scanning / gosec

Potential file inclusion via variable Error

Potential file inclusion via variable
if err != nil {
return err
}

msg.SetAuthzRules(contents)
}

return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
},
}
Expand All @@ -213,6 +229,7 @@ Examples:
cmd.Flags().StringSlice(FlagDenyValidators, []string{}, "Deny validators addresses separated by ,")
cmd.Flags().StringSlice(FlagAllowList, []string{}, "Allowed addresses grantee is allowed to send funds separated by ,")
cmd.Flags().Int64(FlagExpiration, 0, "Expire time as Unix timestamp. Set zero (0) for no expiry. Default is 0.")
cmd.Flags().String(FlagAuthzRules, "", "Rules are conditions to be satisfied when the grant is executed")
return cmd
}

Expand Down
55 changes: 53 additions & 2 deletions x/authz/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,13 @@ func (k Keeper) GetAuthzRulesKeys(ctx context.Context) (map[string]interface{},
return nil, err
}

// rules := map[string]interface{}{
// "Send": []string{"AllowRecipients", "SpendLImit"},
// "Stake": []string{"DelegateLimit"},
// }
rules := map[string]interface{}{
"Send": []string{"AllowRecipients", "SpendLImit"},
"Stake": []string{"DelegateLimit"},
// "Send": []string{"AllowRecipients", "SpendLImit"},
// "Stake": []string{"DelegateLimit"},
}

return rules, nil
Expand Down Expand Up @@ -283,6 +287,53 @@ func (k Keeper) SaveGrant(ctx context.Context, grantee, granter sdk.AccAddress,
})
}

// SaveGrantWithRules method does the same as SaveGrant method but stores rules.
func (k Keeper) SaveGrantWithRules(ctx context.Context, grantee, granter sdk.AccAddress, authorization authz.Authorization, expiration *time.Time, rules []byte) error {
sdkCtx := sdk.UnwrapSDKContext(ctx)
msgType := authorization.MsgTypeURL()
store := k.storeService.OpenKVStore(ctx)
skey := grantStoreKey(grantee, granter, msgType)

grant, err := authz.NewGrantWithRules(sdkCtx.BlockTime(), authorization, expiration, rules)
if err != nil {
return err
}

var oldExp *time.Time
if oldGrant, found := k.getGrant(ctx, skey); found {
oldExp = oldGrant.Expiration
}

if oldExp != nil && (expiration == nil || !oldExp.Equal(*expiration)) {
if err = k.removeFromGrantQueue(ctx, skey, granter, grantee, *oldExp); err != nil {
return err
}
}

// If the expiration didn't change, then we don't remove it and we should not insert again
if expiration != nil && (oldExp == nil || !oldExp.Equal(*expiration)) {
if err = k.insertIntoGrantQueue(ctx, granter, grantee, msgType, *expiration); err != nil {
return err
}
}

bz, err := k.cdc.Marshal(&grant)
if err != nil {
return err
}

err = store.Set(skey, bz)
if err != nil {
return err
}

return sdkCtx.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 context.Context, grantee, granter sdk.AccAddress, msgType string) error {
Expand Down
2 changes: 1 addition & 1 deletion x/authz/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func (k Keeper) Grant(goCtx context.Context, msg *authz.MsgGrant) (*authz.MsgGra
return nil, sdkerrors.ErrInvalidType.Wrapf("%s doesn't exist.", t)
}

err = k.SaveGrant(ctx, grantee, granter, authorization, msg.Grant.Expiration)
err = k.SaveGrantWithRules(ctx, grantee, granter, authorization, msg.Grant.Expiration, msg.Rules)
if err != nil {
return nil, err
}
Expand Down
4 changes: 4 additions & 0 deletions x/authz/msgs.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ func NewMsgGrant(granter, grantee sdk.AccAddress, a Authorization, expiration *t
return m, nil
}

func (msg *MsgGrant) SetAuthzRules(rules []byte) {
msg.Rules = rules
}

// GetAuthorization returns the cache value from the MsgGrant.Authorization if present.
func (msg *MsgGrant) GetAuthorization() (Authorization, error) {
return msg.Grant.GetAuthorization()
Expand Down
Loading

0 comments on commit 7c4fab8

Please sign in to comment.