code.gitea.io/gitea@v1.21.7/routers/web/repo/setting/webhook.go (about)

     1  // Copyright 2015 The Gogs Authors. All rights reserved.
     2  // Copyright 2017 The Gitea Authors. All rights reserved.
     3  // SPDX-License-Identifier: MIT
     4  
     5  package setting
     6  
     7  import (
     8  	"errors"
     9  	"fmt"
    10  	"net/http"
    11  	"net/url"
    12  	"path"
    13  	"strings"
    14  
    15  	"code.gitea.io/gitea/models/perm"
    16  	access_model "code.gitea.io/gitea/models/perm/access"
    17  	user_model "code.gitea.io/gitea/models/user"
    18  	"code.gitea.io/gitea/models/webhook"
    19  	"code.gitea.io/gitea/modules/base"
    20  	"code.gitea.io/gitea/modules/context"
    21  	"code.gitea.io/gitea/modules/git"
    22  	"code.gitea.io/gitea/modules/json"
    23  	"code.gitea.io/gitea/modules/setting"
    24  	api "code.gitea.io/gitea/modules/structs"
    25  	"code.gitea.io/gitea/modules/util"
    26  	"code.gitea.io/gitea/modules/web"
    27  	webhook_module "code.gitea.io/gitea/modules/webhook"
    28  	"code.gitea.io/gitea/services/convert"
    29  	"code.gitea.io/gitea/services/forms"
    30  	webhook_service "code.gitea.io/gitea/services/webhook"
    31  )
    32  
    33  const (
    34  	tplHooks        base.TplName = "repo/settings/webhook/base"
    35  	tplHookNew      base.TplName = "repo/settings/webhook/new"
    36  	tplOrgHookNew   base.TplName = "org/settings/hook_new"
    37  	tplUserHookNew  base.TplName = "user/settings/hook_new"
    38  	tplAdminHookNew base.TplName = "admin/hook_new"
    39  )
    40  
    41  // Webhooks render web hooks list page
    42  func Webhooks(ctx *context.Context) {
    43  	ctx.Data["Title"] = ctx.Tr("repo.settings.hooks")
    44  	ctx.Data["PageIsSettingsHooks"] = true
    45  	ctx.Data["BaseLink"] = ctx.Repo.RepoLink + "/settings/hooks"
    46  	ctx.Data["BaseLinkNew"] = ctx.Repo.RepoLink + "/settings/hooks"
    47  	ctx.Data["Description"] = ctx.Tr("repo.settings.hooks_desc", "https://docs.gitea.com/usage/webhooks")
    48  
    49  	ws, err := webhook.ListWebhooksByOpts(ctx, &webhook.ListWebhookOptions{RepoID: ctx.Repo.Repository.ID})
    50  	if err != nil {
    51  		ctx.ServerError("GetWebhooksByRepoID", err)
    52  		return
    53  	}
    54  	ctx.Data["Webhooks"] = ws
    55  
    56  	ctx.HTML(http.StatusOK, tplHooks)
    57  }
    58  
    59  type ownerRepoCtx struct {
    60  	OwnerID         int64
    61  	RepoID          int64
    62  	IsAdmin         bool
    63  	IsSystemWebhook bool
    64  	Link            string
    65  	LinkNew         string
    66  	NewTemplate     base.TplName
    67  }
    68  
    69  // getOwnerRepoCtx determines whether this is a repo, owner, or admin (both default and system) context.
    70  func getOwnerRepoCtx(ctx *context.Context) (*ownerRepoCtx, error) {
    71  	if ctx.Data["PageIsRepoSettings"] == true {
    72  		return &ownerRepoCtx{
    73  			RepoID:      ctx.Repo.Repository.ID,
    74  			Link:        path.Join(ctx.Repo.RepoLink, "settings/hooks"),
    75  			LinkNew:     path.Join(ctx.Repo.RepoLink, "settings/hooks"),
    76  			NewTemplate: tplHookNew,
    77  		}, nil
    78  	}
    79  
    80  	if ctx.Data["PageIsOrgSettings"] == true {
    81  		return &ownerRepoCtx{
    82  			OwnerID:     ctx.ContextUser.ID,
    83  			Link:        path.Join(ctx.Org.OrgLink, "settings/hooks"),
    84  			LinkNew:     path.Join(ctx.Org.OrgLink, "settings/hooks"),
    85  			NewTemplate: tplOrgHookNew,
    86  		}, nil
    87  	}
    88  
    89  	if ctx.Data["PageIsUserSettings"] == true {
    90  		return &ownerRepoCtx{
    91  			OwnerID:     ctx.Doer.ID,
    92  			Link:        path.Join(setting.AppSubURL, "/user/settings/hooks"),
    93  			LinkNew:     path.Join(setting.AppSubURL, "/user/settings/hooks"),
    94  			NewTemplate: tplUserHookNew,
    95  		}, nil
    96  	}
    97  
    98  	if ctx.Data["PageIsAdmin"] == true {
    99  		return &ownerRepoCtx{
   100  			IsAdmin:         true,
   101  			IsSystemWebhook: ctx.Params(":configType") == "system-hooks",
   102  			Link:            path.Join(setting.AppSubURL, "/admin/hooks"),
   103  			LinkNew:         path.Join(setting.AppSubURL, "/admin/", ctx.Params(":configType")),
   104  			NewTemplate:     tplAdminHookNew,
   105  		}, nil
   106  	}
   107  
   108  	return nil, errors.New("unable to set OwnerRepo context")
   109  }
   110  
   111  func checkHookType(ctx *context.Context) string {
   112  	hookType := strings.ToLower(ctx.Params(":type"))
   113  	if !util.SliceContainsString(setting.Webhook.Types, hookType, true) {
   114  		ctx.NotFound("checkHookType", nil)
   115  		return ""
   116  	}
   117  	return hookType
   118  }
   119  
   120  // WebhooksNew render creating webhook page
   121  func WebhooksNew(ctx *context.Context) {
   122  	ctx.Data["Title"] = ctx.Tr("repo.settings.add_webhook")
   123  	ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook_module.HookEvent{}}
   124  
   125  	orCtx, err := getOwnerRepoCtx(ctx)
   126  	if err != nil {
   127  		ctx.ServerError("getOwnerRepoCtx", err)
   128  		return
   129  	}
   130  
   131  	if orCtx.IsAdmin && orCtx.IsSystemWebhook {
   132  		ctx.Data["PageIsAdminSystemHooks"] = true
   133  		ctx.Data["PageIsAdminSystemHooksNew"] = true
   134  	} else if orCtx.IsAdmin {
   135  		ctx.Data["PageIsAdminDefaultHooks"] = true
   136  		ctx.Data["PageIsAdminDefaultHooksNew"] = true
   137  	} else {
   138  		ctx.Data["PageIsSettingsHooks"] = true
   139  		ctx.Data["PageIsSettingsHooksNew"] = true
   140  	}
   141  
   142  	hookType := checkHookType(ctx)
   143  	ctx.Data["HookType"] = hookType
   144  	if ctx.Written() {
   145  		return
   146  	}
   147  	if hookType == "discord" {
   148  		ctx.Data["DiscordHook"] = map[string]any{
   149  			"Username": "Gitea",
   150  		}
   151  	}
   152  	ctx.Data["BaseLink"] = orCtx.LinkNew
   153  
   154  	ctx.HTML(http.StatusOK, orCtx.NewTemplate)
   155  }
   156  
   157  // ParseHookEvent convert web form content to webhook.HookEvent
   158  func ParseHookEvent(form forms.WebhookForm) *webhook_module.HookEvent {
   159  	return &webhook_module.HookEvent{
   160  		PushOnly:       form.PushOnly(),
   161  		SendEverything: form.SendEverything(),
   162  		ChooseEvents:   form.ChooseEvents(),
   163  		HookEvents: webhook_module.HookEvents{
   164  			Create:                   form.Create,
   165  			Delete:                   form.Delete,
   166  			Fork:                     form.Fork,
   167  			Issues:                   form.Issues,
   168  			IssueAssign:              form.IssueAssign,
   169  			IssueLabel:               form.IssueLabel,
   170  			IssueMilestone:           form.IssueMilestone,
   171  			IssueComment:             form.IssueComment,
   172  			Release:                  form.Release,
   173  			Push:                     form.Push,
   174  			PullRequest:              form.PullRequest,
   175  			PullRequestAssign:        form.PullRequestAssign,
   176  			PullRequestLabel:         form.PullRequestLabel,
   177  			PullRequestMilestone:     form.PullRequestMilestone,
   178  			PullRequestComment:       form.PullRequestComment,
   179  			PullRequestReview:        form.PullRequestReview,
   180  			PullRequestSync:          form.PullRequestSync,
   181  			PullRequestReviewRequest: form.PullRequestReviewRequest,
   182  			Wiki:                     form.Wiki,
   183  			Repository:               form.Repository,
   184  			Package:                  form.Package,
   185  		},
   186  		BranchFilter: form.BranchFilter,
   187  	}
   188  }
   189  
   190  type webhookParams struct {
   191  	// Type should be imported from webhook package (webhook.XXX)
   192  	Type string
   193  
   194  	URL         string
   195  	ContentType webhook.HookContentType
   196  	Secret      string
   197  	HTTPMethod  string
   198  	WebhookForm forms.WebhookForm
   199  	Meta        any
   200  }
   201  
   202  func createWebhook(ctx *context.Context, params webhookParams) {
   203  	ctx.Data["Title"] = ctx.Tr("repo.settings.add_webhook")
   204  	ctx.Data["PageIsSettingsHooks"] = true
   205  	ctx.Data["PageIsSettingsHooksNew"] = true
   206  	ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook_module.HookEvent{}}
   207  	ctx.Data["HookType"] = params.Type
   208  
   209  	orCtx, err := getOwnerRepoCtx(ctx)
   210  	if err != nil {
   211  		ctx.ServerError("getOwnerRepoCtx", err)
   212  		return
   213  	}
   214  	ctx.Data["BaseLink"] = orCtx.LinkNew
   215  
   216  	if ctx.HasError() {
   217  		ctx.HTML(http.StatusOK, orCtx.NewTemplate)
   218  		return
   219  	}
   220  
   221  	var meta []byte
   222  	if params.Meta != nil {
   223  		meta, err = json.Marshal(params.Meta)
   224  		if err != nil {
   225  			ctx.ServerError("Marshal", err)
   226  			return
   227  		}
   228  	}
   229  
   230  	w := &webhook.Webhook{
   231  		RepoID:          orCtx.RepoID,
   232  		URL:             params.URL,
   233  		HTTPMethod:      params.HTTPMethod,
   234  		ContentType:     params.ContentType,
   235  		Secret:          params.Secret,
   236  		HookEvent:       ParseHookEvent(params.WebhookForm),
   237  		IsActive:        params.WebhookForm.Active,
   238  		Type:            params.Type,
   239  		Meta:            string(meta),
   240  		OwnerID:         orCtx.OwnerID,
   241  		IsSystemWebhook: orCtx.IsSystemWebhook,
   242  	}
   243  	err = w.SetHeaderAuthorization(params.WebhookForm.AuthorizationHeader)
   244  	if err != nil {
   245  		ctx.ServerError("SetHeaderAuthorization", err)
   246  		return
   247  	}
   248  	if err := w.UpdateEvent(); err != nil {
   249  		ctx.ServerError("UpdateEvent", err)
   250  		return
   251  	} else if err := webhook.CreateWebhook(ctx, w); err != nil {
   252  		ctx.ServerError("CreateWebhook", err)
   253  		return
   254  	}
   255  
   256  	ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success"))
   257  	ctx.Redirect(orCtx.Link)
   258  }
   259  
   260  func editWebhook(ctx *context.Context, params webhookParams) {
   261  	ctx.Data["Title"] = ctx.Tr("repo.settings.update_webhook")
   262  	ctx.Data["PageIsSettingsHooks"] = true
   263  	ctx.Data["PageIsSettingsHooksEdit"] = true
   264  
   265  	orCtx, w := checkWebhook(ctx)
   266  	if ctx.Written() {
   267  		return
   268  	}
   269  	ctx.Data["Webhook"] = w
   270  
   271  	if ctx.HasError() {
   272  		ctx.HTML(http.StatusOK, orCtx.NewTemplate)
   273  		return
   274  	}
   275  
   276  	var meta []byte
   277  	var err error
   278  	if params.Meta != nil {
   279  		meta, err = json.Marshal(params.Meta)
   280  		if err != nil {
   281  			ctx.ServerError("Marshal", err)
   282  			return
   283  		}
   284  	}
   285  
   286  	w.URL = params.URL
   287  	w.ContentType = params.ContentType
   288  	w.Secret = params.Secret
   289  	w.HookEvent = ParseHookEvent(params.WebhookForm)
   290  	w.IsActive = params.WebhookForm.Active
   291  	w.HTTPMethod = params.HTTPMethod
   292  	w.Meta = string(meta)
   293  
   294  	err = w.SetHeaderAuthorization(params.WebhookForm.AuthorizationHeader)
   295  	if err != nil {
   296  		ctx.ServerError("SetHeaderAuthorization", err)
   297  		return
   298  	}
   299  
   300  	if err := w.UpdateEvent(); err != nil {
   301  		ctx.ServerError("UpdateEvent", err)
   302  		return
   303  	} else if err := webhook.UpdateWebhook(w); err != nil {
   304  		ctx.ServerError("UpdateWebhook", err)
   305  		return
   306  	}
   307  
   308  	ctx.Flash.Success(ctx.Tr("repo.settings.update_hook_success"))
   309  	ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID))
   310  }
   311  
   312  // GiteaHooksNewPost response for creating Gitea webhook
   313  func GiteaHooksNewPost(ctx *context.Context) {
   314  	createWebhook(ctx, giteaHookParams(ctx))
   315  }
   316  
   317  // GiteaHooksEditPost response for editing Gitea webhook
   318  func GiteaHooksEditPost(ctx *context.Context) {
   319  	editWebhook(ctx, giteaHookParams(ctx))
   320  }
   321  
   322  func giteaHookParams(ctx *context.Context) webhookParams {
   323  	form := web.GetForm(ctx).(*forms.NewWebhookForm)
   324  
   325  	contentType := webhook.ContentTypeJSON
   326  	if webhook.HookContentType(form.ContentType) == webhook.ContentTypeForm {
   327  		contentType = webhook.ContentTypeForm
   328  	}
   329  
   330  	return webhookParams{
   331  		Type:        webhook_module.GITEA,
   332  		URL:         form.PayloadURL,
   333  		ContentType: contentType,
   334  		Secret:      form.Secret,
   335  		HTTPMethod:  form.HTTPMethod,
   336  		WebhookForm: form.WebhookForm,
   337  	}
   338  }
   339  
   340  // GogsHooksNewPost response for creating Gogs webhook
   341  func GogsHooksNewPost(ctx *context.Context) {
   342  	createWebhook(ctx, gogsHookParams(ctx))
   343  }
   344  
   345  // GogsHooksEditPost response for editing Gogs webhook
   346  func GogsHooksEditPost(ctx *context.Context) {
   347  	editWebhook(ctx, gogsHookParams(ctx))
   348  }
   349  
   350  func gogsHookParams(ctx *context.Context) webhookParams {
   351  	form := web.GetForm(ctx).(*forms.NewGogshookForm)
   352  
   353  	contentType := webhook.ContentTypeJSON
   354  	if webhook.HookContentType(form.ContentType) == webhook.ContentTypeForm {
   355  		contentType = webhook.ContentTypeForm
   356  	}
   357  
   358  	return webhookParams{
   359  		Type:        webhook_module.GOGS,
   360  		URL:         form.PayloadURL,
   361  		ContentType: contentType,
   362  		Secret:      form.Secret,
   363  		WebhookForm: form.WebhookForm,
   364  	}
   365  }
   366  
   367  // DiscordHooksNewPost response for creating Discord webhook
   368  func DiscordHooksNewPost(ctx *context.Context) {
   369  	createWebhook(ctx, discordHookParams(ctx))
   370  }
   371  
   372  // DiscordHooksEditPost response for editing Discord webhook
   373  func DiscordHooksEditPost(ctx *context.Context) {
   374  	editWebhook(ctx, discordHookParams(ctx))
   375  }
   376  
   377  func discordHookParams(ctx *context.Context) webhookParams {
   378  	form := web.GetForm(ctx).(*forms.NewDiscordHookForm)
   379  
   380  	return webhookParams{
   381  		Type:        webhook_module.DISCORD,
   382  		URL:         form.PayloadURL,
   383  		ContentType: webhook.ContentTypeJSON,
   384  		WebhookForm: form.WebhookForm,
   385  		Meta: &webhook_service.DiscordMeta{
   386  			Username: form.Username,
   387  			IconURL:  form.IconURL,
   388  		},
   389  	}
   390  }
   391  
   392  // DingtalkHooksNewPost response for creating Dingtalk webhook
   393  func DingtalkHooksNewPost(ctx *context.Context) {
   394  	createWebhook(ctx, dingtalkHookParams(ctx))
   395  }
   396  
   397  // DingtalkHooksEditPost response for editing Dingtalk webhook
   398  func DingtalkHooksEditPost(ctx *context.Context) {
   399  	editWebhook(ctx, dingtalkHookParams(ctx))
   400  }
   401  
   402  func dingtalkHookParams(ctx *context.Context) webhookParams {
   403  	form := web.GetForm(ctx).(*forms.NewDingtalkHookForm)
   404  
   405  	return webhookParams{
   406  		Type:        webhook_module.DINGTALK,
   407  		URL:         form.PayloadURL,
   408  		ContentType: webhook.ContentTypeJSON,
   409  		WebhookForm: form.WebhookForm,
   410  	}
   411  }
   412  
   413  // TelegramHooksNewPost response for creating Telegram webhook
   414  func TelegramHooksNewPost(ctx *context.Context) {
   415  	createWebhook(ctx, telegramHookParams(ctx))
   416  }
   417  
   418  // TelegramHooksEditPost response for editing Telegram webhook
   419  func TelegramHooksEditPost(ctx *context.Context) {
   420  	editWebhook(ctx, telegramHookParams(ctx))
   421  }
   422  
   423  func telegramHookParams(ctx *context.Context) webhookParams {
   424  	form := web.GetForm(ctx).(*forms.NewTelegramHookForm)
   425  
   426  	return webhookParams{
   427  		Type:        webhook_module.TELEGRAM,
   428  		URL:         fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage?chat_id=%s&message_thread_id=%s", url.PathEscape(form.BotToken), url.QueryEscape(form.ChatID), url.QueryEscape(form.ThreadID)),
   429  		ContentType: webhook.ContentTypeJSON,
   430  		WebhookForm: form.WebhookForm,
   431  		Meta: &webhook_service.TelegramMeta{
   432  			BotToken: form.BotToken,
   433  			ChatID:   form.ChatID,
   434  			ThreadID: form.ThreadID,
   435  		},
   436  	}
   437  }
   438  
   439  // MatrixHooksNewPost response for creating Matrix webhook
   440  func MatrixHooksNewPost(ctx *context.Context) {
   441  	createWebhook(ctx, matrixHookParams(ctx))
   442  }
   443  
   444  // MatrixHooksEditPost response for editing Matrix webhook
   445  func MatrixHooksEditPost(ctx *context.Context) {
   446  	editWebhook(ctx, matrixHookParams(ctx))
   447  }
   448  
   449  func matrixHookParams(ctx *context.Context) webhookParams {
   450  	form := web.GetForm(ctx).(*forms.NewMatrixHookForm)
   451  
   452  	return webhookParams{
   453  		Type:        webhook_module.MATRIX,
   454  		URL:         fmt.Sprintf("%s/_matrix/client/r0/rooms/%s/send/m.room.message", form.HomeserverURL, url.PathEscape(form.RoomID)),
   455  		ContentType: webhook.ContentTypeJSON,
   456  		HTTPMethod:  http.MethodPut,
   457  		WebhookForm: form.WebhookForm,
   458  		Meta: &webhook_service.MatrixMeta{
   459  			HomeserverURL: form.HomeserverURL,
   460  			Room:          form.RoomID,
   461  			MessageType:   form.MessageType,
   462  		},
   463  	}
   464  }
   465  
   466  // MSTeamsHooksNewPost response for creating MSTeams webhook
   467  func MSTeamsHooksNewPost(ctx *context.Context) {
   468  	createWebhook(ctx, mSTeamsHookParams(ctx))
   469  }
   470  
   471  // MSTeamsHooksEditPost response for editing MSTeams webhook
   472  func MSTeamsHooksEditPost(ctx *context.Context) {
   473  	editWebhook(ctx, mSTeamsHookParams(ctx))
   474  }
   475  
   476  func mSTeamsHookParams(ctx *context.Context) webhookParams {
   477  	form := web.GetForm(ctx).(*forms.NewMSTeamsHookForm)
   478  
   479  	return webhookParams{
   480  		Type:        webhook_module.MSTEAMS,
   481  		URL:         form.PayloadURL,
   482  		ContentType: webhook.ContentTypeJSON,
   483  		WebhookForm: form.WebhookForm,
   484  	}
   485  }
   486  
   487  // SlackHooksNewPost response for creating Slack webhook
   488  func SlackHooksNewPost(ctx *context.Context) {
   489  	createWebhook(ctx, slackHookParams(ctx))
   490  }
   491  
   492  // SlackHooksEditPost response for editing Slack webhook
   493  func SlackHooksEditPost(ctx *context.Context) {
   494  	editWebhook(ctx, slackHookParams(ctx))
   495  }
   496  
   497  func slackHookParams(ctx *context.Context) webhookParams {
   498  	form := web.GetForm(ctx).(*forms.NewSlackHookForm)
   499  
   500  	return webhookParams{
   501  		Type:        webhook_module.SLACK,
   502  		URL:         form.PayloadURL,
   503  		ContentType: webhook.ContentTypeJSON,
   504  		WebhookForm: form.WebhookForm,
   505  		Meta: &webhook_service.SlackMeta{
   506  			Channel:  strings.TrimSpace(form.Channel),
   507  			Username: form.Username,
   508  			IconURL:  form.IconURL,
   509  			Color:    form.Color,
   510  		},
   511  	}
   512  }
   513  
   514  // FeishuHooksNewPost response for creating Feishu webhook
   515  func FeishuHooksNewPost(ctx *context.Context) {
   516  	createWebhook(ctx, feishuHookParams(ctx))
   517  }
   518  
   519  // FeishuHooksEditPost response for editing Feishu webhook
   520  func FeishuHooksEditPost(ctx *context.Context) {
   521  	editWebhook(ctx, feishuHookParams(ctx))
   522  }
   523  
   524  func feishuHookParams(ctx *context.Context) webhookParams {
   525  	form := web.GetForm(ctx).(*forms.NewFeishuHookForm)
   526  
   527  	return webhookParams{
   528  		Type:        webhook_module.FEISHU,
   529  		URL:         form.PayloadURL,
   530  		ContentType: webhook.ContentTypeJSON,
   531  		WebhookForm: form.WebhookForm,
   532  	}
   533  }
   534  
   535  // WechatworkHooksNewPost response for creating Wechatwork webhook
   536  func WechatworkHooksNewPost(ctx *context.Context) {
   537  	createWebhook(ctx, wechatworkHookParams(ctx))
   538  }
   539  
   540  // WechatworkHooksEditPost response for editing Wechatwork webhook
   541  func WechatworkHooksEditPost(ctx *context.Context) {
   542  	editWebhook(ctx, wechatworkHookParams(ctx))
   543  }
   544  
   545  func wechatworkHookParams(ctx *context.Context) webhookParams {
   546  	form := web.GetForm(ctx).(*forms.NewWechatWorkHookForm)
   547  
   548  	return webhookParams{
   549  		Type:        webhook_module.WECHATWORK,
   550  		URL:         form.PayloadURL,
   551  		ContentType: webhook.ContentTypeJSON,
   552  		WebhookForm: form.WebhookForm,
   553  	}
   554  }
   555  
   556  // PackagistHooksNewPost response for creating Packagist webhook
   557  func PackagistHooksNewPost(ctx *context.Context) {
   558  	createWebhook(ctx, packagistHookParams(ctx))
   559  }
   560  
   561  // PackagistHooksEditPost response for editing Packagist webhook
   562  func PackagistHooksEditPost(ctx *context.Context) {
   563  	editWebhook(ctx, packagistHookParams(ctx))
   564  }
   565  
   566  func packagistHookParams(ctx *context.Context) webhookParams {
   567  	form := web.GetForm(ctx).(*forms.NewPackagistHookForm)
   568  
   569  	return webhookParams{
   570  		Type:        webhook_module.PACKAGIST,
   571  		URL:         fmt.Sprintf("https://packagist.org/api/update-package?username=%s&apiToken=%s", url.QueryEscape(form.Username), url.QueryEscape(form.APIToken)),
   572  		ContentType: webhook.ContentTypeJSON,
   573  		WebhookForm: form.WebhookForm,
   574  		Meta: &webhook_service.PackagistMeta{
   575  			Username:   form.Username,
   576  			APIToken:   form.APIToken,
   577  			PackageURL: form.PackageURL,
   578  		},
   579  	}
   580  }
   581  
   582  func checkWebhook(ctx *context.Context) (*ownerRepoCtx, *webhook.Webhook) {
   583  	orCtx, err := getOwnerRepoCtx(ctx)
   584  	if err != nil {
   585  		ctx.ServerError("getOwnerRepoCtx", err)
   586  		return nil, nil
   587  	}
   588  	ctx.Data["BaseLink"] = orCtx.Link
   589  
   590  	var w *webhook.Webhook
   591  	if orCtx.RepoID > 0 {
   592  		w, err = webhook.GetWebhookByRepoID(ctx, orCtx.RepoID, ctx.ParamsInt64(":id"))
   593  	} else if orCtx.OwnerID > 0 {
   594  		w, err = webhook.GetWebhookByOwnerID(ctx, orCtx.OwnerID, ctx.ParamsInt64(":id"))
   595  	} else if orCtx.IsAdmin {
   596  		w, err = webhook.GetSystemOrDefaultWebhook(ctx, ctx.ParamsInt64(":id"))
   597  	}
   598  	if err != nil || w == nil {
   599  		if webhook.IsErrWebhookNotExist(err) {
   600  			ctx.NotFound("GetWebhookByID", nil)
   601  		} else {
   602  			ctx.ServerError("GetWebhookByID", err)
   603  		}
   604  		return nil, nil
   605  	}
   606  
   607  	ctx.Data["HookType"] = w.Type
   608  	switch w.Type {
   609  	case webhook_module.SLACK:
   610  		ctx.Data["SlackHook"] = webhook_service.GetSlackHook(w)
   611  	case webhook_module.DISCORD:
   612  		ctx.Data["DiscordHook"] = webhook_service.GetDiscordHook(w)
   613  	case webhook_module.TELEGRAM:
   614  		ctx.Data["TelegramHook"] = webhook_service.GetTelegramHook(w)
   615  	case webhook_module.MATRIX:
   616  		ctx.Data["MatrixHook"] = webhook_service.GetMatrixHook(w)
   617  	case webhook_module.PACKAGIST:
   618  		ctx.Data["PackagistHook"] = webhook_service.GetPackagistHook(w)
   619  	}
   620  
   621  	ctx.Data["History"], err = w.History(1)
   622  	if err != nil {
   623  		ctx.ServerError("History", err)
   624  	}
   625  	return orCtx, w
   626  }
   627  
   628  // WebHooksEdit render editing web hook page
   629  func WebHooksEdit(ctx *context.Context) {
   630  	ctx.Data["Title"] = ctx.Tr("repo.settings.update_webhook")
   631  	ctx.Data["PageIsSettingsHooks"] = true
   632  	ctx.Data["PageIsSettingsHooksEdit"] = true
   633  
   634  	orCtx, w := checkWebhook(ctx)
   635  	if ctx.Written() {
   636  		return
   637  	}
   638  	ctx.Data["Webhook"] = w
   639  
   640  	ctx.HTML(http.StatusOK, orCtx.NewTemplate)
   641  }
   642  
   643  // TestWebhook test if web hook is work fine
   644  func TestWebhook(ctx *context.Context) {
   645  	hookID := ctx.ParamsInt64(":id")
   646  	w, err := webhook.GetWebhookByRepoID(ctx, ctx.Repo.Repository.ID, hookID)
   647  	if err != nil {
   648  		ctx.Flash.Error("GetWebhookByRepoID: " + err.Error())
   649  		ctx.Status(http.StatusInternalServerError)
   650  		return
   651  	}
   652  
   653  	// Grab latest commit or fake one if it's empty repository.
   654  	commit := ctx.Repo.Commit
   655  	if commit == nil {
   656  		ghost := user_model.NewGhostUser()
   657  		commit = &git.Commit{
   658  			ID:            git.MustIDFromString(git.EmptySHA),
   659  			Author:        ghost.NewGitSig(),
   660  			Committer:     ghost.NewGitSig(),
   661  			CommitMessage: "This is a fake commit",
   662  		}
   663  	}
   664  
   665  	apiUser := convert.ToUserWithAccessMode(ctx, ctx.Doer, perm.AccessModeNone)
   666  
   667  	apiCommit := &api.PayloadCommit{
   668  		ID:      commit.ID.String(),
   669  		Message: commit.Message(),
   670  		URL:     ctx.Repo.Repository.HTMLURL() + "/commit/" + url.PathEscape(commit.ID.String()),
   671  		Author: &api.PayloadUser{
   672  			Name:  commit.Author.Name,
   673  			Email: commit.Author.Email,
   674  		},
   675  		Committer: &api.PayloadUser{
   676  			Name:  commit.Committer.Name,
   677  			Email: commit.Committer.Email,
   678  		},
   679  	}
   680  
   681  	commitID := commit.ID.String()
   682  	p := &api.PushPayload{
   683  		Ref:          git.BranchPrefix + ctx.Repo.Repository.DefaultBranch,
   684  		Before:       commitID,
   685  		After:        commitID,
   686  		CompareURL:   setting.AppURL + ctx.Repo.Repository.ComposeCompareURL(commitID, commitID),
   687  		Commits:      []*api.PayloadCommit{apiCommit},
   688  		TotalCommits: 1,
   689  		HeadCommit:   apiCommit,
   690  		Repo:         convert.ToRepo(ctx, ctx.Repo.Repository, access_model.Permission{AccessMode: perm.AccessModeNone}),
   691  		Pusher:       apiUser,
   692  		Sender:       apiUser,
   693  	}
   694  	if err := webhook_service.PrepareWebhook(ctx, w, webhook_module.HookEventPush, p); err != nil {
   695  		ctx.Flash.Error("PrepareWebhook: " + err.Error())
   696  		ctx.Status(http.StatusInternalServerError)
   697  	} else {
   698  		ctx.Flash.Info(ctx.Tr("repo.settings.webhook.delivery.success"))
   699  		ctx.Status(http.StatusOK)
   700  	}
   701  }
   702  
   703  // ReplayWebhook replays a webhook
   704  func ReplayWebhook(ctx *context.Context) {
   705  	hookTaskUUID := ctx.Params(":uuid")
   706  
   707  	orCtx, w := checkWebhook(ctx)
   708  	if ctx.Written() {
   709  		return
   710  	}
   711  
   712  	if err := webhook_service.ReplayHookTask(ctx, w, hookTaskUUID); err != nil {
   713  		if webhook.IsErrHookTaskNotExist(err) {
   714  			ctx.NotFound("ReplayHookTask", nil)
   715  		} else {
   716  			ctx.ServerError("ReplayHookTask", err)
   717  		}
   718  		return
   719  	}
   720  
   721  	ctx.Flash.Success(ctx.Tr("repo.settings.webhook.delivery.success"))
   722  	ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID))
   723  }
   724  
   725  // DeleteWebhook delete a webhook
   726  func DeleteWebhook(ctx *context.Context) {
   727  	if err := webhook.DeleteWebhookByRepoID(ctx, ctx.Repo.Repository.ID, ctx.FormInt64("id")); err != nil {
   728  		ctx.Flash.Error("DeleteWebhookByRepoID: " + err.Error())
   729  	} else {
   730  		ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success"))
   731  	}
   732  
   733  	ctx.JSONRedirect(ctx.Repo.RepoLink + "/settings/hooks")
   734  }