github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/client/kvstore_api_handler.go (about)

     1  package client
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io"
     7  
     8  	"github.com/keybase/client/go/libkb"
     9  	"github.com/keybase/client/go/protocol/keybase1"
    10  	"golang.org/x/net/context"
    11  )
    12  
    13  type kvStoreAPIHandler struct {
    14  	libkb.Contextified
    15  	kvstore  keybase1.KvstoreClient
    16  	selfTeam string
    17  	indent   bool
    18  }
    19  
    20  func newKVStoreAPIHandler(g *libkb.GlobalContext, indentOutput bool) *kvStoreAPIHandler {
    21  	return &kvStoreAPIHandler{Contextified: libkb.NewContextified(g), indent: indentOutput}
    22  }
    23  
    24  func (t *kvStoreAPIHandler) handle(ctx context.Context, c Call, w io.Writer) error {
    25  	switch c.Params.Version {
    26  	case 0, 1:
    27  		return t.handleV1(ctx, c, w)
    28  	default:
    29  		return ErrInvalidVersion{version: c.Params.Version}
    30  	}
    31  }
    32  
    33  const (
    34  	getEntryMethod = "get"
    35  	putEntryMethod = "put"
    36  	listMethod     = "list"
    37  	delEntryMethod = "del"
    38  )
    39  
    40  var validKvstoreMethodsV1 = map[string]bool{
    41  	getEntryMethod: true,
    42  	putEntryMethod: true,
    43  	listMethod:     true,
    44  	delEntryMethod: true,
    45  }
    46  
    47  func (t *kvStoreAPIHandler) handleV1(ctx context.Context, c Call, w io.Writer) error {
    48  	if !validKvstoreMethodsV1[c.Method] {
    49  		return ErrInvalidMethod{name: c.Method, version: 1}
    50  	}
    51  
    52  	kvstore, err := GetKVStoreClient(t.G())
    53  	if err != nil {
    54  		return err
    55  	}
    56  	t.kvstore = kvstore
    57  
    58  	config, err := GetConfigClient(t.G())
    59  	if err != nil {
    60  		return err
    61  	}
    62  	status, err := config.GetCurrentStatus(context.Background(), 0)
    63  	if err != nil {
    64  		return err
    65  	} else if !status.LoggedIn || status.User == nil {
    66  		return errors.New("not logged in")
    67  	}
    68  
    69  	username := status.User.Username
    70  	t.selfTeam = fmt.Sprintf("%s,%s", username, username)
    71  
    72  	switch c.Method {
    73  	case getEntryMethod:
    74  		return t.getEntry(ctx, c, w)
    75  	case putEntryMethod:
    76  		return t.putEntry(ctx, c, w)
    77  	case listMethod:
    78  		return t.list(ctx, c, w)
    79  	case delEntryMethod:
    80  		return t.deleteEntry(ctx, c, w)
    81  	default:
    82  		return ErrInvalidMethod{name: c.Method, version: 1}
    83  	}
    84  }
    85  
    86  type getEntryOptions struct {
    87  	Team      *string `json:"team,omitempty"`
    88  	Namespace string  `json:"namespace"`
    89  	EntryKey  string  `json:"entryKey"`
    90  }
    91  
    92  func (a *getEntryOptions) Check() error {
    93  	if len(a.Namespace) == 0 {
    94  		return errors.New("`namespace` field required")
    95  	}
    96  	if len(a.EntryKey) == 0 {
    97  		return errors.New("`entryKey` field required")
    98  	}
    99  	return nil
   100  }
   101  
   102  func (t *kvStoreAPIHandler) getEntry(ctx context.Context, c Call, w io.Writer) error {
   103  	var opts getEntryOptions
   104  	if err := unmarshalOptions(c, &opts); err != nil {
   105  		return t.encodeErr(c, err, w)
   106  	}
   107  	if opts.Team == nil {
   108  		opts.Team = &t.selfTeam
   109  	}
   110  	arg := keybase1.GetKVEntryArg{
   111  		SessionID: 0,
   112  		TeamName:  *opts.Team,
   113  		Namespace: opts.Namespace,
   114  		EntryKey:  opts.EntryKey,
   115  	}
   116  	res, err := t.kvstore.GetKVEntry(ctx, arg)
   117  	if err != nil {
   118  		return t.encodeErr(c, err, w)
   119  	}
   120  	return t.encodeResult(c, res, w)
   121  }
   122  
   123  type putEntryOptions struct {
   124  	Team       *string `json:"team,omitempty"`
   125  	Namespace  string  `json:"namespace"`
   126  	EntryKey   string  `json:"entryKey"`
   127  	Revision   *int    `json:"revision"`
   128  	EntryValue string  `json:"entryValue"`
   129  }
   130  
   131  func (a *putEntryOptions) Check() error {
   132  	if len(a.Namespace) == 0 {
   133  		return errors.New("`namespace` field required")
   134  	}
   135  	if len(a.EntryKey) == 0 {
   136  		return errors.New("`entryKey` field required")
   137  	}
   138  	if len(a.EntryValue) == 0 {
   139  		return errors.New("`entryValue` field required")
   140  	}
   141  	if a.Revision != nil && *a.Revision <= 0 {
   142  		return errors.New("if setting optional `revision` field, it needs to be a positive integer")
   143  	}
   144  	return nil
   145  }
   146  
   147  func (t *kvStoreAPIHandler) putEntry(ctx context.Context, c Call, w io.Writer) error {
   148  	var opts putEntryOptions
   149  	if err := unmarshalOptions(c, &opts); err != nil {
   150  		return t.encodeErr(c, err, w)
   151  	}
   152  	if opts.Team == nil {
   153  		opts.Team = &t.selfTeam
   154  	}
   155  	var revision int
   156  	if opts.Revision != nil {
   157  		revision = *opts.Revision
   158  	}
   159  	arg := keybase1.PutKVEntryArg{
   160  		SessionID:  0,
   161  		TeamName:   *opts.Team,
   162  		Namespace:  opts.Namespace,
   163  		EntryKey:   opts.EntryKey,
   164  		Revision:   revision,
   165  		EntryValue: opts.EntryValue,
   166  	}
   167  	res, err := t.kvstore.PutKVEntry(ctx, arg)
   168  	if err != nil {
   169  		return t.encodeErr(c, err, w)
   170  	}
   171  	return t.encodeResult(c, res, w)
   172  }
   173  
   174  type deleteEntryOptions struct {
   175  	Team      *string `json:"team,omitempty"`
   176  	Namespace string  `json:"namespace"`
   177  	EntryKey  string  `json:"entryKey"`
   178  	Revision  *int    `json:"revision"`
   179  }
   180  
   181  func (a *deleteEntryOptions) Check() error {
   182  	if len(a.Namespace) == 0 {
   183  		return errors.New("`namespace` field required")
   184  	}
   185  	if len(a.EntryKey) == 0 {
   186  		return errors.New("`entryKey` field required")
   187  	}
   188  	if a.Revision != nil && *a.Revision <= 0 {
   189  		return errors.New("if setting optional `revision` field, it needs to be a positive integer")
   190  	}
   191  	return nil
   192  }
   193  
   194  func (t *kvStoreAPIHandler) deleteEntry(ctx context.Context, c Call, w io.Writer) error {
   195  	var opts deleteEntryOptions
   196  	if err := unmarshalOptions(c, &opts); err != nil {
   197  		return t.encodeErr(c, err, w)
   198  	}
   199  	if opts.Team == nil {
   200  		opts.Team = &t.selfTeam
   201  	}
   202  	var revision int
   203  	if opts.Revision != nil {
   204  		revision = *opts.Revision
   205  	}
   206  	arg := keybase1.DelKVEntryArg{
   207  		SessionID: 0,
   208  		TeamName:  *opts.Team,
   209  		Namespace: opts.Namespace,
   210  		EntryKey:  opts.EntryKey,
   211  		Revision:  revision,
   212  	}
   213  	res, err := t.kvstore.DelKVEntry(ctx, arg)
   214  	if err != nil {
   215  		return t.encodeErr(c, err, w)
   216  	}
   217  	return t.encodeResult(c, res, w)
   218  }
   219  
   220  type listOptions struct {
   221  	Team      *string `json:"team,omitempty"`
   222  	Namespace string  `json:"namespace"`
   223  }
   224  
   225  func (a *listOptions) Check() error {
   226  	return nil
   227  }
   228  
   229  func (t *kvStoreAPIHandler) list(ctx context.Context, c Call, w io.Writer) error {
   230  	var opts listOptions
   231  	if err := unmarshalOptions(c, &opts); err != nil {
   232  		return t.encodeErr(c, err, w)
   233  	}
   234  	if opts.Team == nil {
   235  		opts.Team = &t.selfTeam
   236  	}
   237  	if len(opts.Namespace) == 0 {
   238  		// listing namespaces
   239  		arg := keybase1.ListKVNamespacesArg{
   240  			SessionID: 0,
   241  			TeamName:  *opts.Team,
   242  		}
   243  		res, err := t.kvstore.ListKVNamespaces(ctx, arg)
   244  		if err != nil {
   245  			return t.encodeErr(c, err, w)
   246  		}
   247  		return t.encodeResult(c, res, w)
   248  	}
   249  	// listing entries inside a namespace
   250  	arg := keybase1.ListKVEntriesArg{
   251  		SessionID: 0,
   252  		TeamName:  *opts.Team,
   253  		Namespace: opts.Namespace,
   254  	}
   255  	res, err := t.kvstore.ListKVEntries(ctx, arg)
   256  	if err != nil {
   257  		return t.encodeErr(c, err, w)
   258  	}
   259  	return t.encodeResult(c, res, w)
   260  }
   261  
   262  func (t *kvStoreAPIHandler) encodeResult(call Call, result interface{}, w io.Writer) error {
   263  	return encodeResult(call, result, w, t.indent)
   264  }
   265  
   266  func statusErrorCode(err error) (code int) {
   267  	if err == nil {
   268  		return 0
   269  	}
   270  	if aerr, ok := err.(libkb.AppStatusError); ok {
   271  		return aerr.Code
   272  	}
   273  	return 0
   274  }
   275  
   276  func (t *kvStoreAPIHandler) encodeErr(call Call, err error, w io.Writer) error {
   277  	errorCode := statusErrorCode(err)
   278  	switch errorCode {
   279  	case 0:
   280  		return encodeErr(call, err, w, t.indent)
   281  	default:
   282  		// e.g. libkb.SCTeamStorageWrongRevision
   283  		r := Reply{
   284  			Error: &CallError{
   285  				Message: err.Error(),
   286  				Code:    errorCode,
   287  			},
   288  		}
   289  		return encodeReply(call, r, w, t.indent)
   290  	}
   291  }