command.go 13 KB

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