gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. // Copyright 2016 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package utils
  4. import (
  5. "net/http"
  6. "strconv"
  7. "strings"
  8. "code.gitea.io/gitea/models/db"
  9. user_model "code.gitea.io/gitea/models/user"
  10. "code.gitea.io/gitea/models/webhook"
  11. "code.gitea.io/gitea/modules/json"
  12. "code.gitea.io/gitea/modules/setting"
  13. api "code.gitea.io/gitea/modules/structs"
  14. "code.gitea.io/gitea/modules/util"
  15. "code.gitea.io/gitea/modules/validation"
  16. webhook_module "code.gitea.io/gitea/modules/webhook"
  17. "code.gitea.io/gitea/services/context"
  18. webhook_service "code.gitea.io/gitea/services/webhook"
  19. )
  20. // ListOwnerHooks lists the webhooks of the provided owner
  21. func ListOwnerHooks(ctx *context.APIContext, owner *user_model.User) {
  22. opts := &webhook.ListWebhookOptions{
  23. ListOptions: GetListOptions(ctx),
  24. OwnerID: owner.ID,
  25. }
  26. hooks, count, err := db.FindAndCount[webhook.Webhook](ctx, opts)
  27. if err != nil {
  28. ctx.APIErrorInternal(err)
  29. return
  30. }
  31. apiHooks := make([]*api.Hook, len(hooks))
  32. for i, hook := range hooks {
  33. apiHooks[i], err = webhook_service.ToHook(owner.HomeLink(), hook)
  34. if err != nil {
  35. ctx.APIErrorInternal(err)
  36. return
  37. }
  38. }
  39. ctx.SetTotalCountHeader(count)
  40. ctx.JSON(http.StatusOK, apiHooks)
  41. }
  42. // GetOwnerHook gets an user or organization webhook. Errors are written to ctx.
  43. func GetOwnerHook(ctx *context.APIContext, ownerID, hookID int64) (*webhook.Webhook, error) {
  44. w, err := webhook.GetWebhookByOwnerID(ctx, ownerID, hookID)
  45. if err != nil {
  46. if webhook.IsErrWebhookNotExist(err) {
  47. ctx.APIErrorNotFound()
  48. } else {
  49. ctx.APIErrorInternal(err)
  50. }
  51. return nil, err
  52. }
  53. return w, nil
  54. }
  55. // GetRepoHook get a repo's webhook. If there is an error, write to `ctx`
  56. // accordingly and return the error
  57. func GetRepoHook(ctx *context.APIContext, repoID, hookID int64) (*webhook.Webhook, error) {
  58. w, err := webhook.GetWebhookByRepoID(ctx, repoID, hookID)
  59. if err != nil {
  60. if webhook.IsErrWebhookNotExist(err) {
  61. ctx.APIErrorNotFound()
  62. } else {
  63. ctx.APIErrorInternal(err)
  64. }
  65. return nil, err
  66. }
  67. return w, nil
  68. }
  69. // checkCreateHookOption check if a CreateHookOption form is valid. If invalid,
  70. // write the appropriate error to `ctx`. Return whether the form is valid
  71. func checkCreateHookOption(ctx *context.APIContext, form *api.CreateHookOption) bool {
  72. if !webhook_service.IsValidHookTaskType(form.Type) {
  73. ctx.APIError(http.StatusUnprocessableEntity, "Invalid hook type: "+form.Type)
  74. return false
  75. }
  76. for _, name := range []string{"url", "content_type"} {
  77. if _, ok := form.Config[name]; !ok {
  78. ctx.APIError(http.StatusUnprocessableEntity, "Missing config option: "+name)
  79. return false
  80. }
  81. }
  82. if !webhook.IsValidHookContentType(form.Config["content_type"]) {
  83. ctx.APIError(http.StatusUnprocessableEntity, "Invalid content type")
  84. return false
  85. }
  86. if !validation.IsValidURL(form.Config["url"]) {
  87. ctx.APIError(http.StatusUnprocessableEntity, "Invalid url")
  88. return false
  89. }
  90. return true
  91. }
  92. // AddSystemHook add a system hook
  93. func AddSystemHook(ctx *context.APIContext, form *api.CreateHookOption) {
  94. hook, ok := addHook(ctx, form, 0, 0)
  95. if ok {
  96. h, err := webhook_service.ToHook(setting.AppSubURL+"/-/admin", hook)
  97. if err != nil {
  98. ctx.APIErrorInternal(err)
  99. return
  100. }
  101. ctx.JSON(http.StatusCreated, h)
  102. }
  103. }
  104. // AddOwnerHook adds a hook to an user or organization
  105. func AddOwnerHook(ctx *context.APIContext, owner *user_model.User, form *api.CreateHookOption) {
  106. hook, ok := addHook(ctx, form, owner.ID, 0)
  107. if !ok {
  108. return
  109. }
  110. apiHook, ok := toAPIHook(ctx, owner.HomeLink(), hook)
  111. if !ok {
  112. return
  113. }
  114. ctx.JSON(http.StatusCreated, apiHook)
  115. }
  116. // AddRepoHook add a hook to a repo. Writes to `ctx` accordingly
  117. func AddRepoHook(ctx *context.APIContext, form *api.CreateHookOption) {
  118. repo := ctx.Repo
  119. hook, ok := addHook(ctx, form, 0, repo.Repository.ID)
  120. if !ok {
  121. return
  122. }
  123. apiHook, ok := toAPIHook(ctx, repo.RepoLink, hook)
  124. if !ok {
  125. return
  126. }
  127. ctx.JSON(http.StatusCreated, apiHook)
  128. }
  129. // toAPIHook converts the hook to its API representation.
  130. // If there is an error, write to `ctx` accordingly. Return (hook, ok)
  131. func toAPIHook(ctx *context.APIContext, repoLink string, hook *webhook.Webhook) (*api.Hook, bool) {
  132. apiHook, err := webhook_service.ToHook(repoLink, hook)
  133. if err != nil {
  134. ctx.APIErrorInternal(err)
  135. return nil, false
  136. }
  137. return apiHook, true
  138. }
  139. func issuesHook(events []string, event string) bool {
  140. return util.SliceContainsString(events, event, true) || util.SliceContainsString(events, string(webhook_module.HookEventIssues), true)
  141. }
  142. func pullHook(events []string, event string) bool {
  143. return util.SliceContainsString(events, event, true) || util.SliceContainsString(events, string(webhook_module.HookEventPullRequest), true)
  144. }
  145. func updateHookEvents(events []string) webhook_module.HookEvents {
  146. if len(events) == 0 {
  147. events = []string{"push"}
  148. }
  149. hookEvents := make(webhook_module.HookEvents)
  150. hookEvents[webhook_module.HookEventCreate] = util.SliceContainsString(events, string(webhook_module.HookEventCreate), true)
  151. hookEvents[webhook_module.HookEventPush] = util.SliceContainsString(events, string(webhook_module.HookEventPush), true)
  152. hookEvents[webhook_module.HookEventDelete] = util.SliceContainsString(events, string(webhook_module.HookEventDelete), true)
  153. hookEvents[webhook_module.HookEventFork] = util.SliceContainsString(events, string(webhook_module.HookEventFork), true)
  154. hookEvents[webhook_module.HookEventRepository] = util.SliceContainsString(events, string(webhook_module.HookEventRepository), true)
  155. hookEvents[webhook_module.HookEventWiki] = util.SliceContainsString(events, string(webhook_module.HookEventWiki), true)
  156. hookEvents[webhook_module.HookEventRelease] = util.SliceContainsString(events, string(webhook_module.HookEventRelease), true)
  157. hookEvents[webhook_module.HookEventPackage] = util.SliceContainsString(events, string(webhook_module.HookEventPackage), true)
  158. hookEvents[webhook_module.HookEventStatus] = util.SliceContainsString(events, string(webhook_module.HookEventStatus), true)
  159. hookEvents[webhook_module.HookEventWorkflowRun] = util.SliceContainsString(events, string(webhook_module.HookEventWorkflowRun), true)
  160. hookEvents[webhook_module.HookEventWorkflowJob] = util.SliceContainsString(events, string(webhook_module.HookEventWorkflowJob), true)
  161. // Issues
  162. hookEvents[webhook_module.HookEventIssues] = issuesHook(events, "issues_only")
  163. hookEvents[webhook_module.HookEventIssueAssign] = issuesHook(events, string(webhook_module.HookEventIssueAssign))
  164. hookEvents[webhook_module.HookEventIssueLabel] = issuesHook(events, string(webhook_module.HookEventIssueLabel))
  165. hookEvents[webhook_module.HookEventIssueMilestone] = issuesHook(events, string(webhook_module.HookEventIssueMilestone))
  166. hookEvents[webhook_module.HookEventIssueComment] = issuesHook(events, string(webhook_module.HookEventIssueComment))
  167. // Pull requests
  168. hookEvents[webhook_module.HookEventPullRequest] = pullHook(events, "pull_request_only")
  169. hookEvents[webhook_module.HookEventPullRequestAssign] = pullHook(events, string(webhook_module.HookEventPullRequestAssign))
  170. hookEvents[webhook_module.HookEventPullRequestLabel] = pullHook(events, string(webhook_module.HookEventPullRequestLabel))
  171. hookEvents[webhook_module.HookEventPullRequestMilestone] = pullHook(events, string(webhook_module.HookEventPullRequestMilestone))
  172. hookEvents[webhook_module.HookEventPullRequestComment] = pullHook(events, string(webhook_module.HookEventPullRequestComment))
  173. hookEvents[webhook_module.HookEventPullRequestReview] = pullHook(events, "pull_request_review")
  174. hookEvents[webhook_module.HookEventPullRequestReviewRequest] = pullHook(events, string(webhook_module.HookEventPullRequestReviewRequest))
  175. hookEvents[webhook_module.HookEventPullRequestSync] = pullHook(events, string(webhook_module.HookEventPullRequestSync))
  176. return hookEvents
  177. }
  178. // addHook add the hook specified by `form`, `ownerID` and `repoID`. If there is
  179. // an error, write to `ctx` accordingly. Return (webhook, ok)
  180. func addHook(ctx *context.APIContext, form *api.CreateHookOption, ownerID, repoID int64) (*webhook.Webhook, bool) {
  181. var isSystemWebhook bool
  182. if !checkCreateHookOption(ctx, form) {
  183. return nil, false
  184. }
  185. if form.Config["is_system_webhook"] != "" {
  186. sw, err := strconv.ParseBool(form.Config["is_system_webhook"])
  187. if err != nil {
  188. ctx.APIError(http.StatusUnprocessableEntity, "Invalid is_system_webhook value")
  189. return nil, false
  190. }
  191. isSystemWebhook = sw
  192. }
  193. w := &webhook.Webhook{
  194. OwnerID: ownerID,
  195. RepoID: repoID,
  196. URL: form.Config["url"],
  197. ContentType: webhook.ToHookContentType(form.Config["content_type"]),
  198. Secret: form.Config["secret"],
  199. HTTPMethod: "POST",
  200. IsSystemWebhook: isSystemWebhook,
  201. HookEvent: &webhook_module.HookEvent{
  202. ChooseEvents: true,
  203. HookEvents: updateHookEvents(form.Events),
  204. BranchFilter: form.BranchFilter,
  205. },
  206. IsActive: form.Active,
  207. Type: form.Type,
  208. }
  209. err := w.SetHeaderAuthorization(form.AuthorizationHeader)
  210. if err != nil {
  211. ctx.APIErrorInternal(err)
  212. return nil, false
  213. }
  214. if w.Type == webhook_module.SLACK {
  215. channel, ok := form.Config["channel"]
  216. if !ok {
  217. ctx.APIError(http.StatusUnprocessableEntity, "Missing config option: channel")
  218. return nil, false
  219. }
  220. channel = strings.TrimSpace(channel)
  221. if !webhook_service.IsValidSlackChannel(channel) {
  222. ctx.APIError(http.StatusBadRequest, "Invalid slack channel name")
  223. return nil, false
  224. }
  225. meta, err := json.Marshal(&webhook_service.SlackMeta{
  226. Channel: channel,
  227. Username: form.Config["username"],
  228. IconURL: form.Config["icon_url"],
  229. Color: form.Config["color"],
  230. })
  231. if err != nil {
  232. ctx.APIErrorInternal(err)
  233. return nil, false
  234. }
  235. w.Meta = string(meta)
  236. }
  237. if err := w.UpdateEvent(); err != nil {
  238. ctx.APIErrorInternal(err)
  239. return nil, false
  240. } else if err := webhook.CreateWebhook(ctx, w); err != nil {
  241. ctx.APIErrorInternal(err)
  242. return nil, false
  243. }
  244. return w, true
  245. }
  246. // EditSystemHook edit system webhook `w` according to `form`. Writes to `ctx` accordingly
  247. func EditSystemHook(ctx *context.APIContext, form *api.EditHookOption, hookID int64) {
  248. hook, err := webhook.GetSystemOrDefaultWebhook(ctx, hookID)
  249. if err != nil {
  250. ctx.APIErrorInternal(err)
  251. return
  252. }
  253. if !editHook(ctx, form, hook) {
  254. ctx.APIErrorInternal(err)
  255. return
  256. }
  257. updated, err := webhook.GetSystemOrDefaultWebhook(ctx, hookID)
  258. if err != nil {
  259. ctx.APIErrorInternal(err)
  260. return
  261. }
  262. h, err := webhook_service.ToHook(setting.AppURL+"/-/admin", updated)
  263. if err != nil {
  264. ctx.APIErrorInternal(err)
  265. return
  266. }
  267. ctx.JSON(http.StatusOK, h)
  268. }
  269. // EditOwnerHook updates a webhook of an user or organization
  270. func EditOwnerHook(ctx *context.APIContext, owner *user_model.User, form *api.EditHookOption, hookID int64) {
  271. hook, err := GetOwnerHook(ctx, owner.ID, hookID)
  272. if err != nil {
  273. return
  274. }
  275. if !editHook(ctx, form, hook) {
  276. return
  277. }
  278. updated, err := GetOwnerHook(ctx, owner.ID, hookID)
  279. if err != nil {
  280. return
  281. }
  282. apiHook, ok := toAPIHook(ctx, owner.HomeLink(), updated)
  283. if !ok {
  284. return
  285. }
  286. ctx.JSON(http.StatusOK, apiHook)
  287. }
  288. // EditRepoHook edit webhook `w` according to `form`. Writes to `ctx` accordingly
  289. func EditRepoHook(ctx *context.APIContext, form *api.EditHookOption, hookID int64) {
  290. repo := ctx.Repo
  291. hook, err := GetRepoHook(ctx, repo.Repository.ID, hookID)
  292. if err != nil {
  293. return
  294. }
  295. if !editHook(ctx, form, hook) {
  296. return
  297. }
  298. updated, err := GetRepoHook(ctx, repo.Repository.ID, hookID)
  299. if err != nil {
  300. return
  301. }
  302. apiHook, ok := toAPIHook(ctx, repo.RepoLink, updated)
  303. if !ok {
  304. return
  305. }
  306. ctx.JSON(http.StatusOK, apiHook)
  307. }
  308. // editHook edit the webhook `w` according to `form`. If an error occurs, write
  309. // to `ctx` accordingly and return the error. Return whether successful
  310. func editHook(ctx *context.APIContext, form *api.EditHookOption, w *webhook.Webhook) bool {
  311. if form.Config != nil {
  312. if url, ok := form.Config["url"]; ok {
  313. if !validation.IsValidURL(url) {
  314. ctx.APIError(http.StatusUnprocessableEntity, "Invalid url")
  315. return false
  316. }
  317. w.URL = url
  318. }
  319. if ct, ok := form.Config["content_type"]; ok {
  320. if !webhook.IsValidHookContentType(ct) {
  321. ctx.APIError(http.StatusUnprocessableEntity, "Invalid content type")
  322. return false
  323. }
  324. w.ContentType = webhook.ToHookContentType(ct)
  325. }
  326. if w.Type == webhook_module.SLACK {
  327. if channel, ok := form.Config["channel"]; ok {
  328. meta, err := json.Marshal(&webhook_service.SlackMeta{
  329. Channel: channel,
  330. Username: form.Config["username"],
  331. IconURL: form.Config["icon_url"],
  332. Color: form.Config["color"],
  333. })
  334. if err != nil {
  335. ctx.APIErrorInternal(err)
  336. return false
  337. }
  338. w.Meta = string(meta)
  339. }
  340. }
  341. }
  342. // Update events
  343. w.HookEvents = updateHookEvents(form.Events)
  344. w.PushOnly = false
  345. w.SendEverything = false
  346. w.ChooseEvents = true
  347. w.BranchFilter = form.BranchFilter
  348. err := w.SetHeaderAuthorization(form.AuthorizationHeader)
  349. if err != nil {
  350. ctx.APIErrorInternal(err)
  351. return false
  352. }
  353. if err := w.UpdateEvent(); err != nil {
  354. ctx.APIErrorInternal(err)
  355. return false
  356. }
  357. if form.Active != nil {
  358. w.IsActive = *form.Active
  359. }
  360. if err := webhook.UpdateWebhook(ctx, w); err != nil {
  361. ctx.APIErrorInternal(err)
  362. return false
  363. }
  364. return true
  365. }
  366. // DeleteOwnerHook deletes the hook owned by the owner.
  367. func DeleteOwnerHook(ctx *context.APIContext, owner *user_model.User, hookID int64) {
  368. if err := webhook.DeleteWebhookByOwnerID(ctx, owner.ID, hookID); err != nil {
  369. if webhook.IsErrWebhookNotExist(err) {
  370. ctx.APIErrorNotFound()
  371. } else {
  372. ctx.APIErrorInternal(err)
  373. }
  374. return
  375. }
  376. ctx.Status(http.StatusNoContent)
  377. }