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 }