github.com/masterhung0112/hk_server/v5@v5.0.0-20220302090640-ec71aef15e1c/api4/command.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See LICENSE.txt for license information. 3 4 package api4 5 6 import ( 7 "net/http" 8 "strconv" 9 "strings" 10 11 "github.com/masterhung0112/hk_server/v5/audit" 12 "github.com/masterhung0112/hk_server/v5/model" 13 ) 14 15 func (api *API) InitCommand() { 16 api.BaseRoutes.Commands.Handle("", api.ApiSessionRequired(createCommand)).Methods("POST") 17 api.BaseRoutes.Commands.Handle("", api.ApiSessionRequired(listCommands)).Methods("GET") 18 api.BaseRoutes.Commands.Handle("/execute", api.ApiSessionRequired(executeCommand)).Methods("POST") 19 20 api.BaseRoutes.Command.Handle("", api.ApiSessionRequired(getCommand)).Methods("GET") 21 api.BaseRoutes.Command.Handle("", api.ApiSessionRequired(updateCommand)).Methods("PUT") 22 api.BaseRoutes.Command.Handle("/move", api.ApiSessionRequired(moveCommand)).Methods("PUT") 23 api.BaseRoutes.Command.Handle("", api.ApiSessionRequired(deleteCommand)).Methods("DELETE") 24 25 api.BaseRoutes.Team.Handle("/commands/autocomplete", api.ApiSessionRequired(listAutocompleteCommands)).Methods("GET") 26 api.BaseRoutes.Team.Handle("/commands/autocomplete_suggestions", api.ApiSessionRequired(listCommandAutocompleteSuggestions)).Methods("GET") 27 api.BaseRoutes.Command.Handle("/regen_token", api.ApiSessionRequired(regenCommandToken)).Methods("PUT") 28 } 29 30 func createCommand(c *Context, w http.ResponseWriter, r *http.Request) { 31 cmd := model.CommandFromJson(r.Body) 32 if cmd == nil { 33 c.SetInvalidParam("command") 34 return 35 } 36 37 auditRec := c.MakeAuditRecord("createCommand", audit.Fail) 38 defer c.LogAuditRec(auditRec) 39 c.LogAudit("attempt") 40 41 if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), cmd.TeamId, model.PERMISSION_MANAGE_SLASH_COMMANDS) { 42 c.SetPermissionError(model.PERMISSION_MANAGE_SLASH_COMMANDS) 43 return 44 } 45 46 cmd.CreatorId = c.AppContext.Session().UserId 47 48 rcmd, err := c.App.CreateCommand(cmd) 49 if err != nil { 50 c.Err = err 51 return 52 } 53 54 auditRec.Success() 55 c.LogAudit("success") 56 auditRec.AddMeta("command", rcmd) 57 58 w.WriteHeader(http.StatusCreated) 59 w.Write([]byte(rcmd.ToJson())) 60 } 61 62 func updateCommand(c *Context, w http.ResponseWriter, r *http.Request) { 63 c.RequireCommandId() 64 if c.Err != nil { 65 return 66 } 67 68 cmd := model.CommandFromJson(r.Body) 69 if cmd == nil || cmd.Id != c.Params.CommandId { 70 c.SetInvalidParam("command") 71 return 72 } 73 74 auditRec := c.MakeAuditRecord("updateCommand", audit.Fail) 75 defer c.LogAuditRec(auditRec) 76 c.LogAudit("attempt") 77 78 oldCmd, err := c.App.GetCommand(c.Params.CommandId) 79 if err != nil { 80 auditRec.AddMeta("command_id", c.Params.CommandId) 81 c.SetCommandNotFoundError() 82 return 83 } 84 auditRec.AddMeta("command", oldCmd) 85 86 if cmd.TeamId != oldCmd.TeamId { 87 c.Err = model.NewAppError("updateCommand", "api.command.team_mismatch.app_error", nil, "user_id="+c.AppContext.Session().UserId, http.StatusBadRequest) 88 return 89 } 90 91 if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), oldCmd.TeamId, model.PERMISSION_MANAGE_SLASH_COMMANDS) { 92 c.LogAudit("fail - inappropriate permissions") 93 // here we return Not_found instead of a permissions error so we don't leak the existence of 94 // a command to someone without permissions for the team it belongs to. 95 c.SetCommandNotFoundError() 96 return 97 } 98 99 if c.AppContext.Session().UserId != oldCmd.CreatorId && !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), oldCmd.TeamId, model.PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS) { 100 c.LogAudit("fail - inappropriate permissions") 101 c.SetPermissionError(model.PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS) 102 return 103 } 104 105 rcmd, err := c.App.UpdateCommand(oldCmd, cmd) 106 if err != nil { 107 c.Err = err 108 return 109 } 110 111 auditRec.Success() 112 c.LogAudit("success") 113 114 w.Write([]byte(rcmd.ToJson())) 115 } 116 117 func moveCommand(c *Context, w http.ResponseWriter, r *http.Request) { 118 c.RequireCommandId() 119 if c.Err != nil { 120 return 121 } 122 123 cmr, err := model.CommandMoveRequestFromJson(r.Body) 124 if err != nil { 125 c.SetInvalidParam("team_id") 126 return 127 } 128 129 auditRec := c.MakeAuditRecord("moveCommand", audit.Fail) 130 defer c.LogAuditRec(auditRec) 131 c.LogAudit("attempt") 132 133 newTeam, appErr := c.App.GetTeam(cmr.TeamId) 134 if appErr != nil { 135 c.Err = appErr 136 return 137 } 138 auditRec.AddMeta("team", newTeam) 139 140 if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), newTeam.Id, model.PERMISSION_MANAGE_SLASH_COMMANDS) { 141 c.LogAudit("fail - inappropriate permissions") 142 c.SetPermissionError(model.PERMISSION_MANAGE_SLASH_COMMANDS) 143 return 144 } 145 146 cmd, appErr := c.App.GetCommand(c.Params.CommandId) 147 if appErr != nil { 148 c.SetCommandNotFoundError() 149 return 150 } 151 auditRec.AddMeta("command", cmd) 152 153 if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), cmd.TeamId, model.PERMISSION_MANAGE_SLASH_COMMANDS) { 154 c.LogAudit("fail - inappropriate permissions") 155 // here we return Not_found instead of a permissions error so we don't leak the existence of 156 // a command to someone without permissions for the team it belongs to. 157 c.SetCommandNotFoundError() 158 return 159 } 160 161 if appErr = c.App.MoveCommand(newTeam, cmd); appErr != nil { 162 c.Err = appErr 163 return 164 } 165 166 auditRec.Success() 167 c.LogAudit("success") 168 169 ReturnStatusOK(w) 170 } 171 172 func deleteCommand(c *Context, w http.ResponseWriter, r *http.Request) { 173 c.RequireCommandId() 174 if c.Err != nil { 175 return 176 } 177 178 auditRec := c.MakeAuditRecord("deleteCommand", audit.Fail) 179 defer c.LogAuditRec(auditRec) 180 c.LogAudit("attempt") 181 182 cmd, err := c.App.GetCommand(c.Params.CommandId) 183 if err != nil { 184 c.SetCommandNotFoundError() 185 return 186 } 187 auditRec.AddMeta("command", cmd) 188 189 if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), cmd.TeamId, model.PERMISSION_MANAGE_SLASH_COMMANDS) { 190 c.LogAudit("fail - inappropriate permissions") 191 // here we return Not_found instead of a permissions error so we don't leak the existence of 192 // a command to someone without permissions for the team it belongs to. 193 c.SetCommandNotFoundError() 194 return 195 } 196 197 if c.AppContext.Session().UserId != cmd.CreatorId && !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), cmd.TeamId, model.PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS) { 198 c.LogAudit("fail - inappropriate permissions") 199 c.SetPermissionError(model.PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS) 200 return 201 } 202 203 err = c.App.DeleteCommand(cmd.Id) 204 if err != nil { 205 c.Err = err 206 return 207 } 208 209 auditRec.Success() 210 c.LogAudit("success") 211 212 ReturnStatusOK(w) 213 } 214 215 func listCommands(c *Context, w http.ResponseWriter, r *http.Request) { 216 customOnly, _ := strconv.ParseBool(r.URL.Query().Get("custom_only")) 217 218 teamId := r.URL.Query().Get("team_id") 219 if teamId == "" { 220 c.SetInvalidParam("team_id") 221 return 222 } 223 224 if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), teamId, model.PERMISSION_VIEW_TEAM) { 225 c.SetPermissionError(model.PERMISSION_VIEW_TEAM) 226 return 227 } 228 229 var commands []*model.Command 230 var err *model.AppError 231 if customOnly { 232 if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), teamId, model.PERMISSION_MANAGE_SLASH_COMMANDS) { 233 c.SetPermissionError(model.PERMISSION_MANAGE_SLASH_COMMANDS) 234 return 235 } 236 commands, err = c.App.ListTeamCommands(teamId) 237 if err != nil { 238 c.Err = err 239 return 240 } 241 } else { 242 //User with no permission should see only system commands 243 if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), teamId, model.PERMISSION_MANAGE_SLASH_COMMANDS) { 244 commands, err = c.App.ListAutocompleteCommands(teamId, c.AppContext.T) 245 if err != nil { 246 c.Err = err 247 return 248 } 249 } else { 250 commands, err = c.App.ListAllCommands(teamId, c.AppContext.T) 251 if err != nil { 252 c.Err = err 253 return 254 } 255 } 256 } 257 258 w.Write([]byte(model.CommandListToJson(commands))) 259 } 260 261 func getCommand(c *Context, w http.ResponseWriter, r *http.Request) { 262 c.RequireCommandId() 263 if c.Err != nil { 264 return 265 } 266 267 cmd, err := c.App.GetCommand(c.Params.CommandId) 268 if err != nil { 269 c.SetCommandNotFoundError() 270 return 271 } 272 273 // check for permissions to view this command; must have perms to view team and 274 // PERMISSION_MANAGE_SLASH_COMMANDS for the team the command belongs to. 275 276 if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), cmd.TeamId, model.PERMISSION_VIEW_TEAM) { 277 // here we return Not_found instead of a permissions error so we don't leak the existence of 278 // a command to someone without permissions for the team it belongs to. 279 c.SetCommandNotFoundError() 280 return 281 } 282 if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), cmd.TeamId, model.PERMISSION_MANAGE_SLASH_COMMANDS) { 283 // again, return not_found to ensure id existence does not leak. 284 c.SetCommandNotFoundError() 285 return 286 } 287 w.Write([]byte(cmd.ToJson())) 288 } 289 290 func executeCommand(c *Context, w http.ResponseWriter, r *http.Request) { 291 commandArgs := model.CommandArgsFromJson(r.Body) 292 if commandArgs == nil { 293 c.SetInvalidParam("command_args") 294 return 295 } 296 297 if len(commandArgs.Command) <= 1 || strings.Index(commandArgs.Command, "/") != 0 || !model.IsValidId(commandArgs.ChannelId) { 298 c.Err = model.NewAppError("executeCommand", "api.command.execute_command.start.app_error", nil, "", http.StatusBadRequest) 299 return 300 } 301 302 auditRec := c.MakeAuditRecord("executeCommand", audit.Fail) 303 defer c.LogAuditRec(auditRec) 304 auditRec.AddMeta("commandargs", commandArgs) 305 306 // checks that user is a member of the specified channel, and that they have permission to use slash commands in it 307 if !c.App.SessionHasPermissionToChannel(*c.AppContext.Session(), commandArgs.ChannelId, model.PERMISSION_USE_SLASH_COMMANDS) { 308 c.SetPermissionError(model.PERMISSION_USE_SLASH_COMMANDS) 309 return 310 } 311 312 channel, err := c.App.GetChannel(commandArgs.ChannelId) 313 if err != nil { 314 c.Err = err 315 return 316 } 317 318 if channel.Type != model.CHANNEL_DIRECT && channel.Type != model.CHANNEL_GROUP { 319 // if this isn't a DM or GM, the team id is implicitly taken from the channel so that slash commands created on 320 // some other team can't be run against this one 321 commandArgs.TeamId = channel.TeamId 322 } else { 323 // if the slash command was used in a DM or GM, ensure that the user is a member of the specified team, so that 324 // they can't just execute slash commands against arbitrary teams 325 if c.AppContext.Session().GetTeamByTeamId(commandArgs.TeamId) == nil { 326 if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PERMISSION_USE_SLASH_COMMANDS) { 327 c.SetPermissionError(model.PERMISSION_USE_SLASH_COMMANDS) 328 return 329 } 330 } 331 } 332 333 commandArgs.UserId = c.AppContext.Session().UserId 334 commandArgs.T = c.AppContext.T 335 commandArgs.SiteURL = c.GetSiteURLHeader() 336 commandArgs.Session = *c.AppContext.Session() 337 338 auditRec.AddMeta("commandargs", commandArgs) // overwrite in case teamid changed 339 340 response, err := c.App.ExecuteCommand(c.AppContext, commandArgs) 341 if err != nil { 342 c.Err = err 343 return 344 } 345 346 auditRec.Success() 347 w.Write([]byte(response.ToJson())) 348 } 349 350 func listAutocompleteCommands(c *Context, w http.ResponseWriter, r *http.Request) { 351 c.RequireTeamId() 352 if c.Err != nil { 353 return 354 } 355 356 if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), c.Params.TeamId, model.PERMISSION_VIEW_TEAM) { 357 c.SetPermissionError(model.PERMISSION_VIEW_TEAM) 358 return 359 } 360 361 commands, err := c.App.ListAutocompleteCommands(c.Params.TeamId, c.AppContext.T) 362 if err != nil { 363 c.Err = err 364 return 365 } 366 367 w.Write([]byte(model.CommandListToJson(commands))) 368 } 369 370 func listCommandAutocompleteSuggestions(c *Context, w http.ResponseWriter, r *http.Request) { 371 c.RequireTeamId() 372 if c.Err != nil { 373 return 374 } 375 if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), c.Params.TeamId, model.PERMISSION_VIEW_TEAM) { 376 c.SetPermissionError(model.PERMISSION_VIEW_TEAM) 377 return 378 } 379 380 roleId := model.SYSTEM_USER_ROLE_ID 381 if c.IsSystemAdmin() { 382 roleId = model.SYSTEM_ADMIN_ROLE_ID 383 } 384 385 query := r.URL.Query() 386 userInput := query.Get("user_input") 387 if userInput == "" { 388 c.SetInvalidParam("userInput") 389 return 390 } 391 userInput = strings.TrimPrefix(userInput, "/") 392 393 commands, err := c.App.ListAutocompleteCommands(c.Params.TeamId, c.AppContext.T) 394 if err != nil { 395 c.Err = err 396 return 397 } 398 399 commandArgs := &model.CommandArgs{ 400 ChannelId: query.Get("channel_id"), 401 TeamId: c.Params.TeamId, 402 RootId: query.Get("root_id"), 403 ParentId: query.Get("parent_id"), 404 UserId: c.AppContext.Session().UserId, 405 T: c.AppContext.T, 406 Session: *c.AppContext.Session(), 407 SiteURL: c.GetSiteURLHeader(), 408 Command: userInput, 409 } 410 411 suggestions := c.App.GetSuggestions(c.AppContext, commandArgs, commands, roleId) 412 413 w.Write(model.AutocompleteSuggestionsToJSON(suggestions)) 414 } 415 416 func regenCommandToken(c *Context, w http.ResponseWriter, r *http.Request) { 417 c.RequireCommandId() 418 if c.Err != nil { 419 return 420 } 421 422 auditRec := c.MakeAuditRecord("regenCommandToken", audit.Fail) 423 defer c.LogAuditRec(auditRec) 424 c.LogAudit("attempt") 425 426 cmd, err := c.App.GetCommand(c.Params.CommandId) 427 if err != nil { 428 auditRec.AddMeta("command_id", c.Params.CommandId) 429 c.SetCommandNotFoundError() 430 return 431 } 432 auditRec.AddMeta("command", cmd) 433 434 if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), cmd.TeamId, model.PERMISSION_MANAGE_SLASH_COMMANDS) { 435 c.LogAudit("fail - inappropriate permissions") 436 // here we return Not_found instead of a permissions error so we don't leak the existence of 437 // a command to someone without permissions for the team it belongs to. 438 c.SetCommandNotFoundError() 439 return 440 } 441 442 if c.AppContext.Session().UserId != cmd.CreatorId && !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), cmd.TeamId, model.PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS) { 443 c.LogAudit("fail - inappropriate permissions") 444 c.SetPermissionError(model.PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS) 445 return 446 } 447 448 rcmd, err := c.App.RegenCommandToken(cmd) 449 if err != nil { 450 c.Err = err 451 return 452 } 453 454 auditRec.Success() 455 c.LogAudit("success") 456 457 resp := make(map[string]string) 458 resp["token"] = rcmd.Token 459 460 w.Write([]byte(model.MapToJson(resp))) 461 }