github.com/decred/politeia@v1.4.0/politeiawww/handlers.go (about) 1 // Copyright (c) 2022 The Decred developers 2 // Use of this source code is governed by an ISC 3 // license that can be found in the LICENSE file. 4 5 package main 6 7 import ( 8 "context" 9 "encoding/json" 10 "fmt" 11 "net/http" 12 "net/http/httputil" 13 "runtime/debug" 14 "time" 15 16 v3 "github.com/decred/politeia/politeiawww/api/http/v3" 17 "github.com/decred/politeia/politeiawww/logger" 18 plugin "github.com/decred/politeia/politeiawww/plugin/v1" 19 "github.com/decred/politeia/util" 20 "github.com/gorilla/csrf" 21 "github.com/pkg/errors" 22 ) 23 24 // handleNotFound handles all invalid routes and returns a 404 to the client. 25 func (p *politeiawww) handleNotFound(w http.ResponseWriter, r *http.Request) { 26 // Log incoming connection 27 log.Debugf("Invalid route: %v %v %v %v", 28 util.RemoteAddr(r), r.Method, r.URL, r.Proto) 29 30 // Trace incoming request 31 log.Tracef("%v", logger.NewLogClosure(func() string { 32 trace, err := httputil.DumpRequest(r, true) 33 if err != nil { 34 trace = []byte(fmt.Sprintf("handleNotFound: DumpRequest %v", err)) 35 } 36 return string(trace) 37 })) 38 39 util.RespondWithJSON(w, http.StatusNotFound, nil) 40 } 41 42 // handleVersion is the request handler for the http v3 VersionRoute. 43 func (p *politeiawww) handleVersion(w http.ResponseWriter, r *http.Request) { 44 log.Tracef("handleVersion") 45 46 // Set the CSRF header. This is the only route 47 // that sets the CSRF header. 48 w.Header().Set(v3.CSRFTokenHeader, csrf.Token(r)) 49 50 plugins := make(map[string]uint32, len(p.plugins)) 51 for _, plugin := range p.plugins { 52 plugins[plugin.ID()] = plugin.Version() 53 } 54 55 util.RespondWithJSON(w, http.StatusOK, 56 v3.VersionReply{ 57 APIVersion: v3.APIVersion, 58 BuildVersion: p.cfg.Version, 59 Plugins: plugins, 60 }) 61 } 62 63 // handlePolicy is the request handler for the http v3 PolicyRoute. 64 func (p *politeiawww) handlePolicy(w http.ResponseWriter, r *http.Request) { 65 log.Tracef("handlePolicy") 66 67 util.RespondWithJSON(w, http.StatusOK, 68 v3.PolicyReply{ 69 ReadBatchLimit: p.cfg.PluginBatchLimit, 70 }) 71 } 72 73 // handleNewUser is the request handler for the http v3 NewUserRoute. 74 func (p *politeiawww) handleNewUser(w http.ResponseWriter, r *http.Request) { 75 log.Tracef("handleNewUser") 76 77 // Decode the request body 78 var cmd v3.Cmd 79 decoder := json.NewDecoder(r.Body) 80 if err := decoder.Decode(&cmd); err != nil { 81 util.RespondWithJSON(w, http.StatusOK, 82 v3.CmdReply{ 83 Error: v3.UserError{ 84 ErrorCode: v3.ErrorCodeInvalidInput, 85 }, 86 }) 87 return 88 } 89 90 // Verify the plugin is the user plugin 91 if p.userManager.ID() != cmd.PluginID { 92 util.RespondWithJSON(w, http.StatusOK, 93 v3.CmdReply{ 94 Error: v3.UserError{ 95 ErrorCode: v3.ErrorCodePluginNotAuthorized, 96 }, 97 }) 98 return 99 } 100 101 // Extract the session data from the request cookies 102 s, err := p.extractSession(r) 103 if err != nil { 104 respondWithError(w, r, 105 "handleNewUser: extractSession: %v", err) 106 return 107 } 108 109 // Execute the plugin command 110 var ( 111 pluginSession = convertSession(s) 112 pluginCmd = convertCmdFromHTTP(cmd) 113 ) 114 pluginReply, err := p.newUserCmd(r.Context(), 115 pluginSession, pluginCmd) 116 if err != nil { 117 respondWithError(w, r, 118 "handleNewUser: pluginNewUser: %v", err) 119 return 120 } 121 122 reply := convertReplyToHTTP(pluginCmd, *pluginReply) 123 124 // Save any updates that were made to the user session 125 err = p.saveUserSession(r, w, s, pluginSession) 126 if err != nil { 127 // The plugin command has already been executed. 128 // Handled the error gracefully. 129 log.Errorf("handleNewUser: saveSession: %v", err) 130 } 131 132 // Send the response 133 util.RespondWithJSON(w, http.StatusOK, reply) 134 } 135 136 // handleWrite is the request handler for the http v3 WriteRoute. 137 func (p *politeiawww) handleWrite(w http.ResponseWriter, r *http.Request) { 138 log.Tracef("handleWrite") 139 140 // Decode the request body 141 var cmd v3.Cmd 142 decoder := json.NewDecoder(r.Body) 143 if err := decoder.Decode(&cmd); err != nil { 144 util.RespondWithJSON(w, http.StatusOK, 145 v3.CmdReply{ 146 Error: v3.UserError{ 147 ErrorCode: v3.ErrorCodeInvalidInput, 148 }, 149 }) 150 return 151 } 152 153 // Verify the plugin exists 154 _, ok := p.plugins[cmd.PluginID] 155 if !ok { 156 util.RespondWithJSON(w, http.StatusOK, 157 v3.CmdReply{ 158 Error: v3.UserError{ 159 ErrorCode: v3.ErrorCodePluginNotFound, 160 }, 161 }) 162 return 163 } 164 165 // Extract the session data from the request cookies 166 s, err := p.extractSession(r) 167 if err != nil { 168 respondWithError(w, r, 169 "handleWrite: extractSession: %v", err) 170 return 171 } 172 173 // Execute the plugin command 174 var ( 175 pluginSession = convertSession(s) 176 pluginCmd = convertCmdFromHTTP(cmd) 177 ) 178 pluginReply, err := p.writeCmd(r.Context(), pluginSession, pluginCmd) 179 if err != nil { 180 respondWithError(w, r, 181 "handleWrite: pluginWrite: %v", err) 182 return 183 } 184 185 reply := convertReplyToHTTP(pluginCmd, *pluginReply) 186 187 // Save any updates that were made to the user session 188 err = p.saveUserSession(r, w, s, pluginSession) 189 if err != nil { 190 // The plugin command has already been executed. 191 // Handled the error gracefully. 192 log.Errorf("handleWrite: saveSession: %v", err) 193 } 194 195 // Send the response 196 util.RespondWithJSON(w, http.StatusOK, reply) 197 } 198 199 // handleRead is the request handler for the http v3 ReadRoute. 200 func (p *politeiawww) handleRead(w http.ResponseWriter, r *http.Request) { 201 log.Tracef("handleRead") 202 203 // Decode the request body 204 var cmd v3.Cmd 205 decoder := json.NewDecoder(r.Body) 206 if err := decoder.Decode(&cmd); err != nil { 207 util.RespondWithJSON(w, http.StatusOK, 208 v3.CmdReply{ 209 Error: v3.UserError{ 210 ErrorCode: v3.ErrorCodeInvalidInput, 211 }, 212 }) 213 return 214 } 215 216 // Verify plugin exists 217 _, ok := p.plugins[cmd.PluginID] 218 if !ok { 219 util.RespondWithJSON(w, http.StatusOK, 220 v3.CmdReply{ 221 Error: v3.UserError{ 222 ErrorCode: v3.ErrorCodePluginNotFound, 223 }, 224 }) 225 return 226 } 227 228 // Extract the session data from the request cookies 229 s, err := p.extractSession(r) 230 if err != nil { 231 respondWithError(w, r, 232 "handleRead: extractSession: %v", err) 233 return 234 } 235 236 // Execute the plugin command 237 var ( 238 pluginSession = convertSession(s) 239 pluginCmd = convertCmdFromHTTP(cmd) 240 ) 241 pluginReply, err := p.readCmd(r.Context(), pluginSession, pluginCmd) 242 if err != nil { 243 respondWithError(w, r, 244 "handleRead: readCmd: %v", err) 245 return 246 } 247 248 reply := convertReplyToHTTP(pluginCmd, *pluginReply) 249 250 // Save any updates that were made to the user session 251 err = p.saveUserSession(r, w, s, pluginSession) 252 if err != nil { 253 // The plugin command has already been executed. 254 // Handle the error gracefully. 255 log.Errorf("handleRead: saveSession: %v", err) 256 } 257 258 // Send the response 259 util.RespondWithJSON(w, http.StatusOK, reply) 260 } 261 262 // handleReadBatch is the request handler for the http v3 ReadBatchRoute. 263 func (p *politeiawww) handleReadBatch(w http.ResponseWriter, r *http.Request) { 264 log.Tracef("handleReadBatch") 265 266 // Decode the request body 267 var batch v3.Batch 268 decoder := json.NewDecoder(r.Body) 269 if err := decoder.Decode(&batch); err != nil { 270 util.RespondWithJSON(w, http.StatusOK, 271 v3.CmdReply{ 272 Error: v3.UserError{ 273 ErrorCode: v3.ErrorCodeInvalidInput, 274 }, 275 }) 276 return 277 } 278 if len(batch.Cmds) > int(p.cfg.PluginBatchLimit) { 279 util.RespondWithJSON(w, http.StatusOK, 280 v3.CmdReply{ 281 Error: v3.UserError{ 282 ErrorCode: v3.ErrorCodeBatchLimitExceeded, 283 ErrorContext: fmt.Sprintf("max number of cmds is %v", 284 p.cfg.PluginBatchLimit), 285 }, 286 }) 287 return 288 } 289 290 // Extract the session data from the request cookies 291 s, err := p.extractSession(r) 292 if err != nil { 293 respondWithError(w, r, 294 "handleReadBatch: extractSession: %v", err) 295 return 296 } 297 298 var ( 299 pluginSession = convertSession(s) 300 replies = make([]v3.CmdReply, len(batch.Cmds)) 301 ) 302 for i, cmd := range batch.Cmds { 303 // Verify plugin exists 304 _, ok := p.plugins[cmd.PluginID] 305 if !ok { 306 replies[i] = v3.CmdReply{ 307 Error: v3.UserError{ 308 ErrorCode: v3.ErrorCodePluginNotFound, 309 }, 310 } 311 continue 312 } 313 314 // Execute the plugin command 315 pluginCmd := convertCmdFromHTTP(cmd) 316 pluginReply, err := p.readCmd(r.Context(), pluginSession, pluginCmd) 317 if err != nil { 318 respondWithError(w, r, 319 "handleReadBatch: readCmd: %v", err) 320 return 321 } 322 323 replies[i] = convertReplyToHTTP(pluginCmd, *pluginReply) 324 } 325 326 // Save any updates that were made to the user session 327 err = p.saveUserSession(r, w, s, pluginSession) 328 if err != nil { 329 // The plugin command has already been executed. Handle 330 // the error gracefully. 331 log.Errorf("handleReadBatch: saveSession: %v", err) 332 } 333 334 // Send the response 335 util.RespondWithJSON(w, http.StatusOK, 336 v3.BatchReply{ 337 Replies: replies, 338 }) 339 } 340 341 // responseWithError checks the error type and responds with the appropriate 342 // HTTP error response. 343 func respondWithError(w http.ResponseWriter, r *http.Request, format string, err error) { 344 // Check if the client dropped the connection 345 if err := r.Context().Err(); err == context.Canceled { 346 log.Infof("%v %v %v %v client aborted connection", 347 util.RemoteAddr(r), r.Method, r.URL, r.Proto) 348 349 // The client dropped the connection. There 350 // is no need to send a response. 351 return 352 } 353 354 // Check if this a user error 355 var ue v3.UserError 356 if errors.As(err, &ue) { 357 m := fmt.Sprintf("%v User error: %v %v", 358 util.RemoteAddr(r), ue.ErrorCode, v3.ErrorCodes[ue.ErrorCode]) 359 if ue.ErrorContext != "" { 360 m += fmt.Sprintf(": %v", ue.ErrorContext) 361 } 362 log.Infof(m) 363 364 util.RespondWithJSON(w, http.StatusOK, 365 v3.CmdReply{ 366 Error: ue, 367 }) 368 return 369 } 370 371 // This is an internal server error. Log it and return a 500. 372 t := time.Now().Unix() 373 e := fmt.Sprintf(format, err) 374 log.Errorf("%v %v %v %v Internal error %v: %v", 375 util.RemoteAddr(r), r.Method, r.URL, r.Proto, t, e) 376 377 // If this is a pkg/errors error then we can pull the 378 // stack trace out of the error, otherwise, we use the 379 // stack trace that points to this function. 380 stack, ok := util.StackTrace(err) 381 if !ok { 382 stack = string(debug.Stack()) 383 } 384 385 log.Errorf("Stacktrace (NOT A REAL CRASH): %v", stack) 386 387 util.RespondWithJSON(w, http.StatusInternalServerError, 388 v3.InternalError{ 389 ErrorCode: t, 390 }) 391 } 392 393 // convertCmdFromHTTP converts a http v3 Cmd to a plugin Cmd. 394 func convertCmdFromHTTP(c v3.Cmd) plugin.Cmd { 395 return plugin.Cmd{ 396 PluginID: c.PluginID, 397 Version: c.Version, 398 Cmd: c.Cmd, 399 Payload: c.Payload, 400 } 401 } 402 403 // convertCmdFromHTTP converts a plugin Reply to a http v3 CmdReply. 404 func convertReplyToHTTP(c plugin.Cmd, r plugin.Reply) v3.CmdReply { 405 return v3.CmdReply{ 406 PluginID: c.PluginID, 407 Version: c.Version, 408 Cmd: c.Cmd, 409 Payload: r.Payload, 410 Error: r.Error, 411 } 412 }