command_test.go 33 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015
  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. "net/http/httptest"
  7. "net/url"
  8. "strings"
  9. "testing"
  10. "github.com/stretchr/testify/assert"
  11. "github.com/stretchr/testify/require"
  12. "github.com/mattermost/mattermost-server/v5/model"
  13. )
  14. func TestCreateCommand(t *testing.T) {
  15. th := Setup(t).InitBasic()
  16. defer th.TearDown()
  17. Client := th.Client
  18. LocalClient := th.LocalClient
  19. enableCommands := *th.App.Config().ServiceSettings.EnableCommands
  20. defer func() {
  21. th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableCommands = &enableCommands })
  22. }()
  23. th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableCommands = true })
  24. newCmd := &model.Command{
  25. CreatorId: th.BasicUser.Id,
  26. TeamId: th.BasicTeam.Id,
  27. URL: "http://nowhere.com",
  28. Method: model.COMMAND_METHOD_POST,
  29. Trigger: "trigger"}
  30. _, resp := Client.CreateCommand(newCmd)
  31. CheckForbiddenStatus(t, resp)
  32. createdCmd, resp := th.SystemAdminClient.CreateCommand(newCmd)
  33. CheckNoError(t, resp)
  34. CheckCreatedStatus(t, resp)
  35. require.Equal(t, th.SystemAdminUser.Id, createdCmd.CreatorId, "user ids didn't match")
  36. require.Equal(t, th.BasicTeam.Id, createdCmd.TeamId, "team ids didn't match")
  37. _, resp = th.SystemAdminClient.CreateCommand(newCmd)
  38. CheckBadRequestStatus(t, resp)
  39. CheckErrorMessage(t, resp, "api.command.duplicate_trigger.app_error")
  40. newCmd.Trigger = "Local"
  41. localCreatedCmd, resp := LocalClient.CreateCommand(newCmd)
  42. CheckNoError(t, resp)
  43. CheckCreatedStatus(t, resp)
  44. require.Equal(t, th.BasicUser.Id, localCreatedCmd.CreatorId, "local client: user ids didn't match")
  45. require.Equal(t, th.BasicTeam.Id, localCreatedCmd.TeamId, "local client: team ids didn't match")
  46. newCmd.Method = "Wrong"
  47. newCmd.Trigger = "testcommand"
  48. _, resp = th.SystemAdminClient.CreateCommand(newCmd)
  49. CheckBadRequestStatus(t, resp)
  50. CheckErrorMessage(t, resp, "model.command.is_valid.method.app_error")
  51. th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableCommands = false })
  52. newCmd.Method = "P"
  53. newCmd.Trigger = "testcommand"
  54. _, resp = th.SystemAdminClient.CreateCommand(newCmd)
  55. CheckNotImplementedStatus(t, resp)
  56. CheckErrorMessage(t, resp, "api.command.disabled.app_error")
  57. // Confirm that local clients can't override disable command setting
  58. newCmd.Trigger = "LocalOverride"
  59. _, resp = LocalClient.CreateCommand(newCmd)
  60. CheckErrorMessage(t, resp, "api.command.disabled.app_error")
  61. }
  62. func TestUpdateCommand(t *testing.T) {
  63. th := Setup(t).InitBasic()
  64. defer th.TearDown()
  65. user := th.SystemAdminUser
  66. team := th.BasicTeam
  67. enableCommands := *th.App.Config().ServiceSettings.EnableCommands
  68. defer func() {
  69. th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableCommands = &enableCommands })
  70. }()
  71. th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableCommands = true })
  72. cmd1 := &model.Command{
  73. CreatorId: user.Id,
  74. TeamId: team.Id,
  75. URL: "http://nowhere.com",
  76. Method: model.COMMAND_METHOD_POST,
  77. Trigger: "trigger1",
  78. }
  79. cmd1, _ = th.App.CreateCommand(cmd1)
  80. cmd2 := &model.Command{
  81. CreatorId: GenerateTestId(),
  82. TeamId: team.Id,
  83. URL: "http://nowhere.com/change",
  84. Method: model.COMMAND_METHOD_GET,
  85. Trigger: "trigger2",
  86. Id: cmd1.Id,
  87. Token: "tokenchange",
  88. }
  89. th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
  90. rcmd, resp := client.UpdateCommand(cmd2)
  91. CheckNoError(t, resp)
  92. require.Equal(t, cmd2.Trigger, rcmd.Trigger, "Trigger should have updated")
  93. require.Equal(t, cmd2.Method, rcmd.Method, "Method should have updated")
  94. require.Equal(t, cmd2.URL, rcmd.URL, "URL should have updated")
  95. require.Equal(t, cmd1.CreatorId, rcmd.CreatorId, "CreatorId should have not updated")
  96. require.Equal(t, cmd1.Token, rcmd.Token, "Token should have not updated")
  97. cmd2.Id = GenerateTestId()
  98. rcmd, resp = client.UpdateCommand(cmd2)
  99. CheckNotFoundStatus(t, resp)
  100. require.Nil(t, rcmd, "should be empty")
  101. cmd2.Id = "junk"
  102. _, resp = client.UpdateCommand(cmd2)
  103. CheckBadRequestStatus(t, resp)
  104. cmd2.Id = cmd1.Id
  105. cmd2.TeamId = GenerateTestId()
  106. _, resp = client.UpdateCommand(cmd2)
  107. CheckBadRequestStatus(t, resp)
  108. cmd2.TeamId = team.Id
  109. _, resp = th.Client.UpdateCommand(cmd2)
  110. CheckNotFoundStatus(t, resp)
  111. })
  112. th.SystemAdminClient.Logout()
  113. _, resp := th.SystemAdminClient.UpdateCommand(cmd2)
  114. CheckUnauthorizedStatus(t, resp)
  115. }
  116. func TestMoveCommand(t *testing.T) {
  117. th := Setup(t).InitBasic()
  118. defer th.TearDown()
  119. user := th.SystemAdminUser
  120. team := th.BasicTeam
  121. newTeam := th.CreateTeam()
  122. enableCommands := *th.App.Config().ServiceSettings.EnableCommands
  123. defer func() {
  124. th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableCommands = &enableCommands })
  125. }()
  126. th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableCommands = true })
  127. cmd1 := &model.Command{
  128. CreatorId: user.Id,
  129. TeamId: team.Id,
  130. URL: "http://nowhere.com",
  131. Method: model.COMMAND_METHOD_POST,
  132. Trigger: "trigger1",
  133. }
  134. rcmd1, _ := th.App.CreateCommand(cmd1)
  135. th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
  136. ok, resp := client.MoveCommand(newTeam.Id, rcmd1.Id)
  137. CheckNoError(t, resp)
  138. require.True(t, ok)
  139. rcmd1, _ = th.App.GetCommand(rcmd1.Id)
  140. require.NotNil(t, rcmd1)
  141. require.Equal(t, newTeam.Id, rcmd1.TeamId)
  142. ok, resp = client.MoveCommand(newTeam.Id, "bogus")
  143. CheckBadRequestStatus(t, resp)
  144. require.False(t, ok)
  145. ok, resp = client.MoveCommand(GenerateTestId(), rcmd1.Id)
  146. CheckNotFoundStatus(t, resp)
  147. require.False(t, ok)
  148. })
  149. cmd2 := &model.Command{
  150. CreatorId: user.Id,
  151. TeamId: team.Id,
  152. URL: "http://nowhere.com",
  153. Method: model.COMMAND_METHOD_POST,
  154. Trigger: "trigger2",
  155. }
  156. rcmd2, _ := th.App.CreateCommand(cmd2)
  157. _, resp := th.Client.MoveCommand(newTeam.Id, rcmd2.Id)
  158. CheckNotFoundStatus(t, resp)
  159. th.SystemAdminClient.Logout()
  160. _, resp = th.SystemAdminClient.MoveCommand(newTeam.Id, rcmd2.Id)
  161. CheckUnauthorizedStatus(t, resp)
  162. }
  163. func TestDeleteCommand(t *testing.T) {
  164. th := Setup(t).InitBasic()
  165. defer th.TearDown()
  166. user := th.SystemAdminUser
  167. team := th.BasicTeam
  168. enableCommands := *th.App.Config().ServiceSettings.EnableCommands
  169. defer func() {
  170. th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableCommands = &enableCommands })
  171. }()
  172. th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableCommands = true })
  173. cmd1 := &model.Command{
  174. CreatorId: user.Id,
  175. TeamId: team.Id,
  176. URL: "http://nowhere.com",
  177. Method: model.COMMAND_METHOD_POST,
  178. Trigger: "trigger1",
  179. }
  180. th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
  181. cmd1.Id = ""
  182. rcmd1, err := th.App.CreateCommand(cmd1)
  183. require.Nil(t, err)
  184. ok, resp := client.DeleteCommand(rcmd1.Id)
  185. CheckNoError(t, resp)
  186. require.True(t, ok)
  187. rcmd1, _ = th.App.GetCommand(rcmd1.Id)
  188. require.Nil(t, rcmd1)
  189. ok, resp = client.DeleteCommand("junk")
  190. CheckBadRequestStatus(t, resp)
  191. require.False(t, ok)
  192. _, resp = client.DeleteCommand(GenerateTestId())
  193. CheckNotFoundStatus(t, resp)
  194. })
  195. cmd2 := &model.Command{
  196. CreatorId: user.Id,
  197. TeamId: team.Id,
  198. URL: "http://nowhere.com",
  199. Method: model.COMMAND_METHOD_POST,
  200. Trigger: "trigger2",
  201. }
  202. rcmd2, _ := th.App.CreateCommand(cmd2)
  203. _, resp := th.Client.DeleteCommand(rcmd2.Id)
  204. CheckNotFoundStatus(t, resp)
  205. th.SystemAdminClient.Logout()
  206. _, resp = th.SystemAdminClient.DeleteCommand(rcmd2.Id)
  207. CheckUnauthorizedStatus(t, resp)
  208. }
  209. func TestListCommands(t *testing.T) {
  210. th := Setup(t).InitBasic()
  211. defer th.TearDown()
  212. Client := th.Client
  213. enableCommands := *th.App.Config().ServiceSettings.EnableCommands
  214. defer func() {
  215. th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableCommands = &enableCommands })
  216. }()
  217. th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableCommands = true })
  218. newCmd := &model.Command{
  219. CreatorId: th.BasicUser.Id,
  220. TeamId: th.BasicTeam.Id,
  221. URL: "http://nowhere.com",
  222. Method: model.COMMAND_METHOD_POST,
  223. Trigger: "custom_command"}
  224. _, resp := th.SystemAdminClient.CreateCommand(newCmd)
  225. CheckNoError(t, resp)
  226. th.TestForSystemAdminAndLocal(t, func(t *testing.T, c *model.Client4) {
  227. listCommands, resp := c.ListCommands(th.BasicTeam.Id, false)
  228. CheckNoError(t, resp)
  229. foundEcho := false
  230. foundCustom := false
  231. for _, command := range listCommands {
  232. if command.Trigger == "echo" {
  233. foundEcho = true
  234. }
  235. if command.Trigger == "custom_command" {
  236. foundCustom = true
  237. }
  238. }
  239. require.True(t, foundEcho, "Couldn't find echo command")
  240. require.True(t, foundCustom, "Should list the custom command")
  241. }, "ListSystemAndCustomCommands")
  242. th.TestForSystemAdminAndLocal(t, func(t *testing.T, c *model.Client4) {
  243. listCommands, resp := c.ListCommands(th.BasicTeam.Id, true)
  244. CheckNoError(t, resp)
  245. require.Len(t, listCommands, 1, "Should list just one custom command")
  246. require.Equal(t, listCommands[0].Trigger, "custom_command", "Wrong custom command trigger")
  247. }, "ListCustomOnlyCommands")
  248. t.Run("UserWithNoPermissionForCustomCommands", func(t *testing.T) {
  249. _, resp := Client.ListCommands(th.BasicTeam.Id, true)
  250. CheckForbiddenStatus(t, resp)
  251. })
  252. t.Run("RegularUserCanListOnlySystemCommands", func(t *testing.T) {
  253. listCommands, resp := Client.ListCommands(th.BasicTeam.Id, false)
  254. CheckNoError(t, resp)
  255. foundEcho := false
  256. foundCustom := false
  257. for _, command := range listCommands {
  258. if command.Trigger == "echo" {
  259. foundEcho = true
  260. }
  261. if command.Trigger == "custom_command" {
  262. foundCustom = true
  263. }
  264. }
  265. require.True(t, foundEcho, "Couldn't find echo command")
  266. require.False(t, foundCustom, "Should not list the custom command")
  267. })
  268. t.Run("NoMember", func(t *testing.T) {
  269. Client.Logout()
  270. user := th.CreateUser()
  271. th.SystemAdminClient.RemoveTeamMember(th.BasicTeam.Id, user.Id)
  272. Client.Login(user.Email, user.Password)
  273. _, resp := Client.ListCommands(th.BasicTeam.Id, false)
  274. CheckForbiddenStatus(t, resp)
  275. _, resp = Client.ListCommands(th.BasicTeam.Id, true)
  276. CheckForbiddenStatus(t, resp)
  277. })
  278. t.Run("NotLoggedIn", func(t *testing.T) {
  279. Client.Logout()
  280. _, resp := Client.ListCommands(th.BasicTeam.Id, false)
  281. CheckUnauthorizedStatus(t, resp)
  282. _, resp = Client.ListCommands(th.BasicTeam.Id, true)
  283. CheckUnauthorizedStatus(t, resp)
  284. })
  285. }
  286. func TestListAutocompleteCommands(t *testing.T) {
  287. th := Setup(t).InitBasic()
  288. defer th.TearDown()
  289. Client := th.Client
  290. newCmd := &model.Command{
  291. CreatorId: th.BasicUser.Id,
  292. TeamId: th.BasicTeam.Id,
  293. URL: "http://nowhere.com",
  294. Method: model.COMMAND_METHOD_POST,
  295. Trigger: "custom_command"}
  296. _, resp := th.SystemAdminClient.CreateCommand(newCmd)
  297. CheckNoError(t, resp)
  298. t.Run("ListAutocompleteCommandsOnly", func(t *testing.T) {
  299. listCommands, resp := th.SystemAdminClient.ListAutocompleteCommands(th.BasicTeam.Id)
  300. CheckNoError(t, resp)
  301. foundEcho := false
  302. foundCustom := false
  303. for _, command := range listCommands {
  304. if command.Trigger == "echo" {
  305. foundEcho = true
  306. }
  307. if command.Trigger == "custom_command" {
  308. foundCustom = true
  309. }
  310. }
  311. require.True(t, foundEcho, "Couldn't find echo command")
  312. require.False(t, foundCustom, "Should not list the custom command")
  313. })
  314. t.Run("RegularUserCanListOnlySystemCommands", func(t *testing.T) {
  315. listCommands, resp := Client.ListAutocompleteCommands(th.BasicTeam.Id)
  316. CheckNoError(t, resp)
  317. foundEcho := false
  318. foundCustom := false
  319. for _, command := range listCommands {
  320. if command.Trigger == "echo" {
  321. foundEcho = true
  322. }
  323. if command.Trigger == "custom_command" {
  324. foundCustom = true
  325. }
  326. }
  327. require.True(t, foundEcho, "Couldn't find echo command")
  328. require.False(t, foundCustom, "Should not list the custom command")
  329. })
  330. t.Run("NoMember", func(t *testing.T) {
  331. Client.Logout()
  332. user := th.CreateUser()
  333. th.SystemAdminClient.RemoveTeamMember(th.BasicTeam.Id, user.Id)
  334. Client.Login(user.Email, user.Password)
  335. _, resp := Client.ListAutocompleteCommands(th.BasicTeam.Id)
  336. CheckForbiddenStatus(t, resp)
  337. })
  338. t.Run("NotLoggedIn", func(t *testing.T) {
  339. Client.Logout()
  340. _, resp := Client.ListAutocompleteCommands(th.BasicTeam.Id)
  341. CheckUnauthorizedStatus(t, resp)
  342. })
  343. }
  344. func TestListCommandAutocompleteSuggestions(t *testing.T) {
  345. th := Setup(t).InitBasic()
  346. defer th.TearDown()
  347. Client := th.Client
  348. newCmd := &model.Command{
  349. CreatorId: th.BasicUser.Id,
  350. TeamId: th.BasicTeam.Id,
  351. URL: "http://nowhere.com",
  352. Method: model.COMMAND_METHOD_POST,
  353. Trigger: "custom_command"}
  354. _, resp := th.SystemAdminClient.CreateCommand(newCmd)
  355. CheckNoError(t, resp)
  356. t.Run("ListAutocompleteSuggestionsOnly", func(t *testing.T) {
  357. suggestions, resp := th.SystemAdminClient.ListCommandAutocompleteSuggestions("/", th.BasicTeam.Id)
  358. CheckNoError(t, resp)
  359. foundEcho := false
  360. foundShrug := false
  361. foundCustom := false
  362. for _, command := range suggestions {
  363. if command.Suggestion == "echo" {
  364. foundEcho = true
  365. }
  366. if command.Suggestion == "shrug" {
  367. foundShrug = true
  368. }
  369. if command.Suggestion == "custom_command" {
  370. foundCustom = true
  371. }
  372. }
  373. require.True(t, foundEcho, "Couldn't find echo command")
  374. require.True(t, foundShrug, "Couldn't find shrug command")
  375. require.False(t, foundCustom, "Should not list the custom command")
  376. })
  377. t.Run("ListAutocompleteSuggestionsOnlyWithInput", func(t *testing.T) {
  378. suggestions, resp := th.SystemAdminClient.ListCommandAutocompleteSuggestions("/e", th.BasicTeam.Id)
  379. CheckNoError(t, resp)
  380. foundEcho := false
  381. foundShrug := false
  382. for _, command := range suggestions {
  383. if command.Suggestion == "echo" {
  384. foundEcho = true
  385. }
  386. if command.Suggestion == "shrug" {
  387. foundShrug = true
  388. }
  389. }
  390. require.True(t, foundEcho, "Couldn't find echo command")
  391. require.False(t, foundShrug, "Should not list the shrug command")
  392. })
  393. t.Run("RegularUserCanListOnlySystemCommands", func(t *testing.T) {
  394. suggestions, resp := Client.ListCommandAutocompleteSuggestions("/", th.BasicTeam.Id)
  395. CheckNoError(t, resp)
  396. foundEcho := false
  397. foundCustom := false
  398. for _, suggestion := range suggestions {
  399. if suggestion.Suggestion == "echo" {
  400. foundEcho = true
  401. }
  402. if suggestion.Suggestion == "custom_command" {
  403. foundCustom = true
  404. }
  405. }
  406. require.True(t, foundEcho, "Couldn't find echo command")
  407. require.False(t, foundCustom, "Should not list the custom command")
  408. })
  409. t.Run("NoMember", func(t *testing.T) {
  410. Client.Logout()
  411. user := th.CreateUser()
  412. th.SystemAdminClient.RemoveTeamMember(th.BasicTeam.Id, user.Id)
  413. Client.Login(user.Email, user.Password)
  414. _, resp := Client.ListCommandAutocompleteSuggestions("/", th.BasicTeam.Id)
  415. CheckForbiddenStatus(t, resp)
  416. })
  417. t.Run("NotLoggedIn", func(t *testing.T) {
  418. Client.Logout()
  419. _, resp := Client.ListCommandAutocompleteSuggestions("/", th.BasicTeam.Id)
  420. CheckUnauthorizedStatus(t, resp)
  421. })
  422. }
  423. func TestGetCommand(t *testing.T) {
  424. th := Setup(t).InitBasic()
  425. defer th.TearDown()
  426. enableCommands := *th.App.Config().ServiceSettings.EnableCommands
  427. defer func() {
  428. th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableCommands = &enableCommands })
  429. }()
  430. th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableCommands = true })
  431. newCmd := &model.Command{
  432. CreatorId: th.BasicUser.Id,
  433. TeamId: th.BasicTeam.Id,
  434. URL: "http://nowhere.com",
  435. Method: model.COMMAND_METHOD_POST,
  436. Trigger: "roger"}
  437. newCmd, resp := th.SystemAdminClient.CreateCommand(newCmd)
  438. CheckNoError(t, resp)
  439. th.TestForSystemAdminAndLocal(t, func(t *testing.T, client *model.Client4) {
  440. t.Run("ValidId", func(t *testing.T) {
  441. cmd, resp := client.GetCommandById(newCmd.Id)
  442. CheckNoError(t, resp)
  443. require.Equal(t, newCmd.Id, cmd.Id)
  444. require.Equal(t, newCmd.CreatorId, cmd.CreatorId)
  445. require.Equal(t, newCmd.TeamId, cmd.TeamId)
  446. require.Equal(t, newCmd.URL, cmd.URL)
  447. require.Equal(t, newCmd.Method, cmd.Method)
  448. require.Equal(t, newCmd.Trigger, cmd.Trigger)
  449. })
  450. t.Run("InvalidId", func(t *testing.T) {
  451. _, resp := client.GetCommandById(strings.Repeat("z", len(newCmd.Id)))
  452. require.Error(t, resp.Error)
  453. })
  454. })
  455. t.Run("UserWithNoPermissionForCustomCommands", func(t *testing.T) {
  456. _, resp := th.Client.GetCommandById(newCmd.Id)
  457. CheckNotFoundStatus(t, resp)
  458. })
  459. t.Run("NoMember", func(t *testing.T) {
  460. th.Client.Logout()
  461. user := th.CreateUser()
  462. th.SystemAdminClient.RemoveTeamMember(th.BasicTeam.Id, user.Id)
  463. th.Client.Login(user.Email, user.Password)
  464. _, resp := th.Client.GetCommandById(newCmd.Id)
  465. CheckNotFoundStatus(t, resp)
  466. })
  467. t.Run("NotLoggedIn", func(t *testing.T) {
  468. th.Client.Logout()
  469. _, resp := th.Client.GetCommandById(newCmd.Id)
  470. CheckUnauthorizedStatus(t, resp)
  471. })
  472. }
  473. func TestRegenToken(t *testing.T) {
  474. th := Setup(t).InitBasic()
  475. defer th.TearDown()
  476. Client := th.Client
  477. enableCommands := *th.App.Config().ServiceSettings.EnableCommands
  478. defer func() {
  479. th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableCommands = &enableCommands })
  480. }()
  481. th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableCommands = true })
  482. newCmd := &model.Command{
  483. CreatorId: th.BasicUser.Id,
  484. TeamId: th.BasicTeam.Id,
  485. URL: "http://nowhere.com",
  486. Method: model.COMMAND_METHOD_POST,
  487. Trigger: "trigger"}
  488. createdCmd, resp := th.SystemAdminClient.CreateCommand(newCmd)
  489. CheckNoError(t, resp)
  490. CheckCreatedStatus(t, resp)
  491. token, resp := th.SystemAdminClient.RegenCommandToken(createdCmd.Id)
  492. CheckNoError(t, resp)
  493. require.NotEqual(t, createdCmd.Token, token, "should update the token")
  494. token, resp = Client.RegenCommandToken(createdCmd.Id)
  495. CheckNotFoundStatus(t, resp)
  496. require.Empty(t, token, "should not return the token")
  497. }
  498. func TestExecuteInvalidCommand(t *testing.T) {
  499. th := Setup(t).InitBasic()
  500. defer th.TearDown()
  501. Client := th.Client
  502. channel := th.BasicChannel
  503. enableCommands := *th.App.Config().ServiceSettings.EnableCommands
  504. allowedInternalConnections := *th.App.Config().ServiceSettings.AllowedUntrustedInternalConnections
  505. defer func() {
  506. th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableCommands = &enableCommands })
  507. th.App.UpdateConfig(func(cfg *model.Config) {
  508. cfg.ServiceSettings.AllowedUntrustedInternalConnections = &allowedInternalConnections
  509. })
  510. }()
  511. th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableCommands = true })
  512. th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowedUntrustedInternalConnections = "127.0.0.0/8" })
  513. ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  514. rc := &model.CommandResponse{}
  515. w.Write([]byte(rc.ToJson()))
  516. }))
  517. defer ts.Close()
  518. getCmd := &model.Command{
  519. CreatorId: th.BasicUser.Id,
  520. TeamId: th.BasicTeam.Id,
  521. URL: ts.URL,
  522. Method: model.COMMAND_METHOD_GET,
  523. Trigger: "getcommand",
  524. }
  525. _, err := th.App.CreateCommand(getCmd)
  526. require.Nil(t, err, "failed to create get command")
  527. _, resp := Client.ExecuteCommand(channel.Id, "")
  528. CheckBadRequestStatus(t, resp)
  529. _, resp = Client.ExecuteCommand(channel.Id, "/")
  530. CheckBadRequestStatus(t, resp)
  531. _, resp = Client.ExecuteCommand(channel.Id, "getcommand")
  532. CheckBadRequestStatus(t, resp)
  533. _, resp = Client.ExecuteCommand(channel.Id, "/junk")
  534. CheckNotFoundStatus(t, resp)
  535. otherUser := th.CreateUser()
  536. Client.Login(otherUser.Email, otherUser.Password)
  537. _, resp = Client.ExecuteCommand(channel.Id, "/getcommand")
  538. CheckForbiddenStatus(t, resp)
  539. Client.Logout()
  540. _, resp = Client.ExecuteCommand(channel.Id, "/getcommand")
  541. CheckUnauthorizedStatus(t, resp)
  542. _, resp = th.SystemAdminClient.ExecuteCommand(channel.Id, "/getcommand")
  543. CheckNoError(t, resp)
  544. }
  545. func TestExecuteGetCommand(t *testing.T) {
  546. th := Setup(t).InitBasic()
  547. defer th.TearDown()
  548. Client := th.Client
  549. channel := th.BasicChannel
  550. enableCommands := *th.App.Config().ServiceSettings.EnableCommands
  551. allowedInternalConnections := *th.App.Config().ServiceSettings.AllowedUntrustedInternalConnections
  552. defer func() {
  553. th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableCommands = &enableCommands })
  554. th.App.UpdateConfig(func(cfg *model.Config) {
  555. cfg.ServiceSettings.AllowedUntrustedInternalConnections = &allowedInternalConnections
  556. })
  557. }()
  558. th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableCommands = true })
  559. th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowedUntrustedInternalConnections = "127.0.0.0/8" })
  560. token := model.NewId()
  561. expectedCommandResponse := &model.CommandResponse{
  562. Text: "test get command response",
  563. ResponseType: model.COMMAND_RESPONSE_TYPE_IN_CHANNEL,
  564. Type: "custom_test",
  565. Props: map[string]interface{}{"someprop": "somevalue"},
  566. }
  567. ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  568. require.Equal(t, http.MethodGet, r.Method)
  569. values, err := url.ParseQuery(r.URL.RawQuery)
  570. require.NoError(t, err)
  571. require.Equal(t, token, values.Get("token"))
  572. require.Equal(t, th.BasicTeam.Name, values.Get("team_domain"))
  573. require.Equal(t, "ourCommand", values.Get("cmd"))
  574. w.Header().Set("Content-Type", "application/json")
  575. w.Write([]byte(expectedCommandResponse.ToJson()))
  576. }))
  577. defer ts.Close()
  578. getCmd := &model.Command{
  579. CreatorId: th.BasicUser.Id,
  580. TeamId: th.BasicTeam.Id,
  581. URL: ts.URL + "/?cmd=ourCommand",
  582. Method: model.COMMAND_METHOD_GET,
  583. Trigger: "getcommand",
  584. Token: token,
  585. }
  586. _, err := th.App.CreateCommand(getCmd)
  587. require.Nil(t, err, "failed to create get command")
  588. commandResponse, resp := Client.ExecuteCommand(channel.Id, "/getcommand")
  589. CheckNoError(t, resp)
  590. assert.True(t, len(commandResponse.TriggerId) == 26)
  591. expectedCommandResponse.TriggerId = commandResponse.TriggerId
  592. require.Equal(t, expectedCommandResponse, commandResponse)
  593. }
  594. func TestExecutePostCommand(t *testing.T) {
  595. th := Setup(t).InitBasic()
  596. defer th.TearDown()
  597. Client := th.Client
  598. channel := th.BasicChannel
  599. enableCommands := *th.App.Config().ServiceSettings.EnableCommands
  600. allowedInternalConnections := *th.App.Config().ServiceSettings.AllowedUntrustedInternalConnections
  601. defer func() {
  602. th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableCommands = &enableCommands })
  603. th.App.UpdateConfig(func(cfg *model.Config) {
  604. cfg.ServiceSettings.AllowedUntrustedInternalConnections = &allowedInternalConnections
  605. })
  606. }()
  607. th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableCommands = true })
  608. th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.AllowedUntrustedInternalConnections = "127.0.0.0/8" })
  609. token := model.NewId()
  610. expectedCommandResponse := &model.CommandResponse{
  611. Text: "test post command response",
  612. ResponseType: model.COMMAND_RESPONSE_TYPE_IN_CHANNEL,
  613. Type: "custom_test",
  614. Props: map[string]interface{}{"someprop": "somevalue"},
  615. }
  616. ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  617. require.Equal(t, http.MethodPost, r.Method)
  618. r.ParseForm()
  619. require.Equal(t, token, r.FormValue("token"))
  620. require.Equal(t, th.BasicTeam.Name, r.FormValue("team_domain"))
  621. w.Header().Set("Content-Type", "application/json")
  622. w.Write([]byte(expectedCommandResponse.ToJson()))
  623. }))
  624. defer ts.Close()
  625. postCmd := &model.Command{
  626. CreatorId: th.BasicUser.Id,
  627. TeamId: th.BasicTeam.Id,
  628. URL: ts.URL,
  629. Method: model.COMMAND_METHOD_POST,
  630. Trigger: "postcommand",
  631. Token: token,
  632. }
  633. _, err := th.App.CreateCommand(postCmd)
  634. require.Nil(t, err, "failed to create get command")
  635. commandResponse, resp := Client.ExecuteCommand(channel.Id, "/postcommand")
  636. CheckNoError(t, resp)
  637. assert.True(t, len(commandResponse.TriggerId) == 26)
  638. expectedCommandResponse.TriggerId = commandResponse.TriggerId
  639. require.Equal(t, expectedCommandResponse, commandResponse)
  640. }
  641. func TestExecuteCommandAgainstChannelOnAnotherTeam(t *testing.T) {
  642. th := Setup(t).InitBasic()
  643. defer th.TearDown()
  644. Client := th.Client
  645. channel := th.BasicChannel
  646. enableCommands := *th.App.Config().ServiceSettings.EnableCommands
  647. allowedInternalConnections := *th.App.Config().ServiceSettings.AllowedUntrustedInternalConnections
  648. defer func() {
  649. th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableCommands = &enableCommands })
  650. th.App.UpdateConfig(func(cfg *model.Config) {
  651. cfg.ServiceSettings.AllowedUntrustedInternalConnections = &allowedInternalConnections
  652. })
  653. }()
  654. th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableCommands = true })
  655. th.App.UpdateConfig(func(cfg *model.Config) {
  656. *cfg.ServiceSettings.AllowedUntrustedInternalConnections = "localhost,127.0.0.1"
  657. })
  658. expectedCommandResponse := &model.CommandResponse{
  659. Text: "test post command response",
  660. ResponseType: model.COMMAND_RESPONSE_TYPE_IN_CHANNEL,
  661. Type: "custom_test",
  662. Props: map[string]interface{}{"someprop": "somevalue"},
  663. }
  664. ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  665. w.Header().Set("Content-Type", "application/json")
  666. w.Write([]byte(expectedCommandResponse.ToJson()))
  667. }))
  668. defer ts.Close()
  669. // create a slash command on some other team where we have permission to do so
  670. team2 := th.CreateTeam()
  671. postCmd := &model.Command{
  672. CreatorId: th.BasicUser.Id,
  673. TeamId: team2.Id,
  674. URL: ts.URL,
  675. Method: model.COMMAND_METHOD_POST,
  676. Trigger: "postcommand",
  677. }
  678. _, err := th.App.CreateCommand(postCmd)
  679. require.Nil(t, err, "failed to create post command")
  680. // the execute command endpoint will always search for the command by trigger and team id, inferring team id from the
  681. // channel id, so there is no way to use that slash command on a channel that belongs to some other team
  682. _, resp := Client.ExecuteCommand(channel.Id, "/postcommand")
  683. CheckNotFoundStatus(t, resp)
  684. }
  685. func TestExecuteCommandAgainstChannelUserIsNotIn(t *testing.T) {
  686. th := Setup(t).InitBasic()
  687. defer th.TearDown()
  688. client := th.Client
  689. enableCommands := *th.App.Config().ServiceSettings.EnableCommands
  690. allowedInternalConnections := *th.App.Config().ServiceSettings.AllowedUntrustedInternalConnections
  691. defer func() {
  692. th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableCommands = &enableCommands })
  693. th.App.UpdateConfig(func(cfg *model.Config) {
  694. cfg.ServiceSettings.AllowedUntrustedInternalConnections = &allowedInternalConnections
  695. })
  696. }()
  697. th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableCommands = true })
  698. th.App.UpdateConfig(func(cfg *model.Config) {
  699. *cfg.ServiceSettings.AllowedUntrustedInternalConnections = "localhost,127.0.0.1"
  700. })
  701. expectedCommandResponse := &model.CommandResponse{
  702. Text: "test post command response",
  703. ResponseType: model.COMMAND_RESPONSE_TYPE_IN_CHANNEL,
  704. Type: "custom_test",
  705. Props: map[string]interface{}{"someprop": "somevalue"},
  706. }
  707. ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  708. w.Header().Set("Content-Type", "application/json")
  709. w.Write([]byte(expectedCommandResponse.ToJson()))
  710. }))
  711. defer ts.Close()
  712. // create a slash command on some other team where we have permission to do so
  713. team2 := th.CreateTeam()
  714. postCmd := &model.Command{
  715. CreatorId: th.BasicUser.Id,
  716. TeamId: team2.Id,
  717. URL: ts.URL,
  718. Method: model.COMMAND_METHOD_POST,
  719. Trigger: "postcommand",
  720. }
  721. _, err := th.App.CreateCommand(postCmd)
  722. require.Nil(t, err, "failed to create post command")
  723. // make a channel on that team, ensuring that our test user isn't in it
  724. channel2 := th.CreateChannelWithClientAndTeam(client, model.CHANNEL_OPEN, team2.Id)
  725. success, _ := client.RemoveUserFromChannel(channel2.Id, th.BasicUser.Id)
  726. require.True(t, success, "Failed to remove user from channel")
  727. // we should not be able to run the slash command in channel2, because we aren't in it
  728. _, resp := client.ExecuteCommandWithTeam(channel2.Id, team2.Id, "/postcommand")
  729. CheckForbiddenStatus(t, resp)
  730. }
  731. func TestExecuteCommandInDirectMessageChannel(t *testing.T) {
  732. th := Setup(t).InitBasic()
  733. defer th.TearDown()
  734. client := th.Client
  735. enableCommands := *th.App.Config().ServiceSettings.EnableCommands
  736. allowedInternalConnections := *th.App.Config().ServiceSettings.AllowedUntrustedInternalConnections
  737. defer func() {
  738. th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableCommands = &enableCommands })
  739. th.App.UpdateConfig(func(cfg *model.Config) {
  740. cfg.ServiceSettings.AllowedUntrustedInternalConnections = &allowedInternalConnections
  741. })
  742. }()
  743. th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableCommands = true })
  744. th.App.UpdateConfig(func(cfg *model.Config) {
  745. *cfg.ServiceSettings.AllowedUntrustedInternalConnections = "localhost,127.0.0.1"
  746. })
  747. // create a team that the user isn't a part of
  748. team2 := th.CreateTeam()
  749. expectedCommandResponse := &model.CommandResponse{
  750. Text: "test post command response",
  751. ResponseType: model.COMMAND_RESPONSE_TYPE_IN_CHANNEL,
  752. Type: "custom_test",
  753. Props: map[string]interface{}{"someprop": "somevalue"},
  754. }
  755. ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  756. require.Equal(t, http.MethodPost, r.Method)
  757. w.Header().Set("Content-Type", "application/json")
  758. w.Write([]byte(expectedCommandResponse.ToJson()))
  759. }))
  760. defer ts.Close()
  761. // create a slash command on some other team where we have permission to do so
  762. postCmd := &model.Command{
  763. CreatorId: th.BasicUser.Id,
  764. TeamId: team2.Id,
  765. URL: ts.URL,
  766. Method: model.COMMAND_METHOD_POST,
  767. Trigger: "postcommand",
  768. }
  769. _, err := th.App.CreateCommand(postCmd)
  770. require.Nil(t, err, "failed to create post command")
  771. // make a direct message channel
  772. dmChannel, response := client.CreateDirectChannel(th.BasicUser.Id, th.BasicUser2.Id)
  773. CheckCreatedStatus(t, response)
  774. // we should be able to run the slash command in the DM channel
  775. _, resp := client.ExecuteCommandWithTeam(dmChannel.Id, team2.Id, "/postcommand")
  776. CheckOKStatus(t, resp)
  777. // but we can't run the slash command in the DM channel if we sub in some other team's id
  778. _, resp = client.ExecuteCommandWithTeam(dmChannel.Id, th.BasicTeam.Id, "/postcommand")
  779. CheckNotFoundStatus(t, resp)
  780. }
  781. func TestExecuteCommandInTeamUserIsNotOn(t *testing.T) {
  782. th := Setup(t).InitBasic()
  783. defer th.TearDown()
  784. client := th.Client
  785. enableCommands := *th.App.Config().ServiceSettings.EnableCommands
  786. allowedInternalConnections := *th.App.Config().ServiceSettings.AllowedUntrustedInternalConnections
  787. defer func() {
  788. th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableCommands = &enableCommands })
  789. th.App.UpdateConfig(func(cfg *model.Config) {
  790. cfg.ServiceSettings.AllowedUntrustedInternalConnections = &allowedInternalConnections
  791. })
  792. }()
  793. th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableCommands = true })
  794. th.App.UpdateConfig(func(cfg *model.Config) {
  795. *cfg.ServiceSettings.AllowedUntrustedInternalConnections = "localhost,127.0.0.1"
  796. })
  797. // create a team that the user isn't a part of
  798. team2 := th.CreateTeam()
  799. expectedCommandResponse := &model.CommandResponse{
  800. Text: "test post command response",
  801. ResponseType: model.COMMAND_RESPONSE_TYPE_IN_CHANNEL,
  802. Type: "custom_test",
  803. Props: map[string]interface{}{"someprop": "somevalue"},
  804. }
  805. ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  806. require.Equal(t, http.MethodPost, r.Method)
  807. r.ParseForm()
  808. require.Equal(t, team2.Name, r.FormValue("team_domain"))
  809. w.Header().Set("Content-Type", "application/json")
  810. w.Write([]byte(expectedCommandResponse.ToJson()))
  811. }))
  812. defer ts.Close()
  813. // create a slash command on that team
  814. postCmd := &model.Command{
  815. CreatorId: th.BasicUser.Id,
  816. TeamId: team2.Id,
  817. URL: ts.URL,
  818. Method: model.COMMAND_METHOD_POST,
  819. Trigger: "postcommand",
  820. }
  821. _, err := th.App.CreateCommand(postCmd)
  822. require.Nil(t, err, "failed to create post command")
  823. // make a direct message channel
  824. dmChannel, response := client.CreateDirectChannel(th.BasicUser.Id, th.BasicUser2.Id)
  825. CheckCreatedStatus(t, response)
  826. // we should be able to run the slash command in the DM channel
  827. _, resp := client.ExecuteCommandWithTeam(dmChannel.Id, team2.Id, "/postcommand")
  828. CheckOKStatus(t, resp)
  829. // if the user is removed from the team, they should NOT be able to run the slash command in the DM channel
  830. success, _ := client.RemoveTeamMember(team2.Id, th.BasicUser.Id)
  831. require.True(t, success, "Failed to remove user from team")
  832. _, resp = client.ExecuteCommandWithTeam(dmChannel.Id, team2.Id, "/postcommand")
  833. CheckForbiddenStatus(t, resp)
  834. // if we omit the team id from the request, the slash command will fail because this is a DM channel, and the
  835. // team id can't be inherited from the channel
  836. _, resp = client.ExecuteCommand(dmChannel.Id, "/postcommand")
  837. CheckForbiddenStatus(t, resp)
  838. }