mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-26 12:27:06 +00:00 
			
		
		
		
	refactor webhook *NewPost (#20729)
* refactor webhook *NewPost * remove empty values * always show errs.Message * remove utils.IsValidSlackChannel * move IsValidSlackChannel to services/webhook package * binding: handle empty Message case * make IsValidSlackChannel more strict
This commit is contained in:
		| @@ -136,7 +136,16 @@ func Validate(errs binding.Errors, data map[string]interface{}, f Form, l transl | |||||||
| 			case validation.ErrRegexPattern: | 			case validation.ErrRegexPattern: | ||||||
| 				data["ErrorMsg"] = trName + l.Tr("form.regex_pattern_error", errs[0].Message) | 				data["ErrorMsg"] = trName + l.Tr("form.regex_pattern_error", errs[0].Message) | ||||||
| 			default: | 			default: | ||||||
| 				data["ErrorMsg"] = l.Tr("form.unknown_error") + " " + errs[0].Classification | 				msg := errs[0].Classification | ||||||
|  | 				if msg != "" && errs[0].Message != "" { | ||||||
|  | 					msg += ": " | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				msg += errs[0].Message | ||||||
|  | 				if msg == "" { | ||||||
|  | 					msg = l.Tr("form.unknown_error") | ||||||
|  | 				} | ||||||
|  | 				data["ErrorMsg"] = trName + ": " + msg | ||||||
| 			} | 			} | ||||||
| 			return errs | 			return errs | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -15,7 +15,6 @@ import ( | |||||||
| 	"code.gitea.io/gitea/modules/json" | 	"code.gitea.io/gitea/modules/json" | ||||||
| 	api "code.gitea.io/gitea/modules/structs" | 	api "code.gitea.io/gitea/modules/structs" | ||||||
| 	"code.gitea.io/gitea/modules/util" | 	"code.gitea.io/gitea/modules/util" | ||||||
| 	"code.gitea.io/gitea/routers/utils" |  | ||||||
| 	webhook_service "code.gitea.io/gitea/services/webhook" | 	webhook_service "code.gitea.io/gitea/services/webhook" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -141,14 +140,15 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, orgID, repoID | |||||||
| 			ctx.Error(http.StatusUnprocessableEntity, "", "Missing config option: channel") | 			ctx.Error(http.StatusUnprocessableEntity, "", "Missing config option: channel") | ||||||
| 			return nil, false | 			return nil, false | ||||||
| 		} | 		} | ||||||
|  | 		channel = strings.TrimSpace(channel) | ||||||
|  |  | ||||||
| 		if !utils.IsValidSlackChannel(channel) { | 		if !webhook_service.IsValidSlackChannel(channel) { | ||||||
| 			ctx.Error(http.StatusBadRequest, "", "Invalid slack channel name") | 			ctx.Error(http.StatusBadRequest, "", "Invalid slack channel name") | ||||||
| 			return nil, false | 			return nil, false | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		meta, err := json.Marshal(&webhook_service.SlackMeta{ | 		meta, err := json.Marshal(&webhook_service.SlackMeta{ | ||||||
| 			Channel:  strings.TrimSpace(channel), | 			Channel:  channel, | ||||||
| 			Username: form.Config["username"], | 			Username: form.Config["username"], | ||||||
| 			IconURL:  form.Config["icon_url"], | 			IconURL:  form.Config["icon_url"], | ||||||
| 			Color:    form.Config["color"], | 			Color:    form.Config["color"], | ||||||
|   | |||||||
| @@ -20,25 +20,6 @@ func RemoveUsernameParameterSuffix(name string) string { | |||||||
| 	return name | 	return name | ||||||
| } | } | ||||||
|  |  | ||||||
| // IsValidSlackChannel validates a channel name conforms to what slack expects. |  | ||||||
| // It makes sure a channel name cannot be empty and invalid ( only an # ) |  | ||||||
| func IsValidSlackChannel(channelName string) bool { |  | ||||||
| 	switch len(strings.TrimSpace(channelName)) { |  | ||||||
| 	case 0: |  | ||||||
| 		return false |  | ||||||
| 	case 1: |  | ||||||
| 		// Keep default behaviour where a channel name is still |  | ||||||
| 		// valid without an # |  | ||||||
| 		// But if it contains only an #, it should be regarded as |  | ||||||
| 		// invalid |  | ||||||
| 		if channelName[0] == '#' { |  | ||||||
| 			return false |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return true |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // SanitizeFlashErrorString will sanitize a flash error string | // SanitizeFlashErrorString will sanitize a flash error string | ||||||
| func SanitizeFlashErrorString(x string) string { | func SanitizeFlashErrorString(x string) string { | ||||||
| 	return strings.ReplaceAll(html.EscapeString(x), "\n", "<br>") | 	return strings.ReplaceAll(html.EscapeString(x), "\n", "<br>") | ||||||
|   | |||||||
| @@ -18,23 +18,6 @@ func TestRemoveUsernameParameterSuffix(t *testing.T) { | |||||||
| 	assert.Equal(t, "", RemoveUsernameParameterSuffix("")) | 	assert.Equal(t, "", RemoveUsernameParameterSuffix("")) | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestIsValidSlackChannel(t *testing.T) { |  | ||||||
| 	tt := []struct { |  | ||||||
| 		channelName string |  | ||||||
| 		expected    bool |  | ||||||
| 	}{ |  | ||||||
| 		{"gitea", true}, |  | ||||||
| 		{"  ", false}, |  | ||||||
| 		{"#", false}, |  | ||||||
| 		{"gitea   ", true}, |  | ||||||
| 		{"  gitea", true}, |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	for _, v := range tt { |  | ||||||
| 		assert.Equal(t, v.expected, IsValidSlackChannel(v.channelName)) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func TestIsExternalURL(t *testing.T) { | func TestIsExternalURL(t *testing.T) { | ||||||
| 	setting.AppURL = "https://try.gitea.io/" | 	setting.AppURL = "https://try.gitea.io/" | ||||||
| 	type test struct { | 	type test struct { | ||||||
|   | |||||||
| @@ -185,14 +185,22 @@ func ParseHookEvent(form forms.WebhookForm) *webhook.HookEvent { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| // GiteaHooksNewPost response for creating Gitea webhook | type webhookCreationParams struct { | ||||||
| func GiteaHooksNewPost(ctx *context.Context) { | 	URL         string | ||||||
| 	form := web.GetForm(ctx).(*forms.NewWebhookForm) | 	ContentType webhook.HookContentType | ||||||
|  | 	Secret      string | ||||||
|  | 	HTTPMethod  string | ||||||
|  | 	WebhookForm forms.WebhookForm | ||||||
|  | 	Type        string | ||||||
|  | 	Meta        interface{} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func createWebhook(ctx *context.Context, params webhookCreationParams) { | ||||||
| 	ctx.Data["Title"] = ctx.Tr("repo.settings.add_webhook") | 	ctx.Data["Title"] = ctx.Tr("repo.settings.add_webhook") | ||||||
| 	ctx.Data["PageIsSettingsHooks"] = true | 	ctx.Data["PageIsSettingsHooks"] = true | ||||||
| 	ctx.Data["PageIsSettingsHooksNew"] = true | 	ctx.Data["PageIsSettingsHooksNew"] = true | ||||||
| 	ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook.HookEvent{}} | 	ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook.HookEvent{}} | ||||||
| 	ctx.Data["HookType"] = webhook.GITEA | 	ctx.Data["HookType"] = params.Type | ||||||
|  |  | ||||||
| 	orCtx, err := getOrgRepoCtx(ctx) | 	orCtx, err := getOrgRepoCtx(ctx) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -206,20 +214,25 @@ func GiteaHooksNewPost(ctx *context.Context) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	contentType := webhook.ContentTypeJSON | 	var meta []byte | ||||||
| 	if webhook.HookContentType(form.ContentType) == webhook.ContentTypeForm { | 	if params.Meta != nil { | ||||||
| 		contentType = webhook.ContentTypeForm | 		meta, err = json.Marshal(params.Meta) | ||||||
|  | 		if err != nil { | ||||||
|  | 			ctx.ServerError("Marshal", err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	w := &webhook.Webhook{ | 	w := &webhook.Webhook{ | ||||||
| 		RepoID:          orCtx.RepoID, | 		RepoID:          orCtx.RepoID, | ||||||
| 		URL:             form.PayloadURL, | 		URL:             params.URL, | ||||||
| 		HTTPMethod:      form.HTTPMethod, | 		HTTPMethod:      params.HTTPMethod, | ||||||
| 		ContentType:     contentType, | 		ContentType:     params.ContentType, | ||||||
| 		Secret:          form.Secret, | 		Secret:          params.Secret, | ||||||
| 		HookEvent:       ParseHookEvent(form.WebhookForm), | 		HookEvent:       ParseHookEvent(params.WebhookForm), | ||||||
| 		IsActive:        form.Active, | 		IsActive:        params.WebhookForm.Active, | ||||||
| 		Type:            webhook.GITEA, | 		Type:            params.Type, | ||||||
|  | 		Meta:            string(meta), | ||||||
| 		OrgID:           orCtx.OrgID, | 		OrgID:           orCtx.OrgID, | ||||||
| 		IsSystemWebhook: orCtx.IsSystemWebhook, | 		IsSystemWebhook: orCtx.IsSystemWebhook, | ||||||
| 	} | 	} | ||||||
| @@ -235,503 +248,175 @@ func GiteaHooksNewPost(ctx *context.Context) { | |||||||
| 	ctx.Redirect(orCtx.Link) | 	ctx.Redirect(orCtx.Link) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // GiteaHooksNewPost response for creating Gitea webhook | ||||||
|  | func GiteaHooksNewPost(ctx *context.Context) { | ||||||
|  | 	form := web.GetForm(ctx).(*forms.NewWebhookForm) | ||||||
|  |  | ||||||
|  | 	contentType := webhook.ContentTypeJSON | ||||||
|  | 	if webhook.HookContentType(form.ContentType) == webhook.ContentTypeForm { | ||||||
|  | 		contentType = webhook.ContentTypeForm | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	createWebhook(ctx, webhookCreationParams{ | ||||||
|  | 		URL:         form.PayloadURL, | ||||||
|  | 		ContentType: contentType, | ||||||
|  | 		Secret:      form.Secret, | ||||||
|  | 		HTTPMethod:  form.HTTPMethod, | ||||||
|  | 		WebhookForm: form.WebhookForm, | ||||||
|  | 		Type:        webhook.GITEA, | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  |  | ||||||
| // GogsHooksNewPost response for creating webhook | // GogsHooksNewPost response for creating webhook | ||||||
| func GogsHooksNewPost(ctx *context.Context) { | func GogsHooksNewPost(ctx *context.Context) { | ||||||
| 	form := web.GetForm(ctx).(*forms.NewGogshookForm) | 	form := web.GetForm(ctx).(*forms.NewGogshookForm) | ||||||
| 	newGogsWebhookPost(ctx, *form, webhook.GOGS) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // newGogsWebhookPost response for creating gogs hook |  | ||||||
| func newGogsWebhookPost(ctx *context.Context, form forms.NewGogshookForm, kind webhook.HookType) { |  | ||||||
| 	ctx.Data["Title"] = ctx.Tr("repo.settings.add_webhook") |  | ||||||
| 	ctx.Data["PageIsSettingsHooks"] = true |  | ||||||
| 	ctx.Data["PageIsSettingsHooksNew"] = true |  | ||||||
| 	ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook.HookEvent{}} |  | ||||||
| 	ctx.Data["HookType"] = webhook.GOGS |  | ||||||
|  |  | ||||||
| 	orCtx, err := getOrgRepoCtx(ctx) |  | ||||||
| 	if err != nil { |  | ||||||
| 		ctx.ServerError("getOrgRepoCtx", err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	ctx.Data["BaseLink"] = orCtx.LinkNew |  | ||||||
|  |  | ||||||
| 	if ctx.HasError() { |  | ||||||
| 		ctx.HTML(http.StatusOK, orCtx.NewTemplate) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	contentType := webhook.ContentTypeJSON | 	contentType := webhook.ContentTypeJSON | ||||||
| 	if webhook.HookContentType(form.ContentType) == webhook.ContentTypeForm { | 	if webhook.HookContentType(form.ContentType) == webhook.ContentTypeForm { | ||||||
| 		contentType = webhook.ContentTypeForm | 		contentType = webhook.ContentTypeForm | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	w := &webhook.Webhook{ | 	createWebhook(ctx, webhookCreationParams{ | ||||||
| 		RepoID:          orCtx.RepoID, | 		URL:         form.PayloadURL, | ||||||
| 		URL:             form.PayloadURL, | 		ContentType: contentType, | ||||||
| 		ContentType:     contentType, | 		Secret:      form.Secret, | ||||||
| 		Secret:          form.Secret, | 		WebhookForm: form.WebhookForm, | ||||||
| 		HookEvent:       ParseHookEvent(form.WebhookForm), | 		Type:        webhook.GOGS, | ||||||
| 		IsActive:        form.Active, | 	}) | ||||||
| 		Type:            kind, |  | ||||||
| 		OrgID:           orCtx.OrgID, |  | ||||||
| 		IsSystemWebhook: orCtx.IsSystemWebhook, |  | ||||||
| 	} |  | ||||||
| 	if err := w.UpdateEvent(); err != nil { |  | ||||||
| 		ctx.ServerError("UpdateEvent", err) |  | ||||||
| 		return |  | ||||||
| 	} else if err := webhook.CreateWebhook(ctx, w); err != nil { |  | ||||||
| 		ctx.ServerError("CreateWebhook", err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success")) |  | ||||||
| 	ctx.Redirect(orCtx.Link) |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // DiscordHooksNewPost response for creating discord hook | // DiscordHooksNewPost response for creating discord hook | ||||||
| func DiscordHooksNewPost(ctx *context.Context) { | func DiscordHooksNewPost(ctx *context.Context) { | ||||||
| 	form := web.GetForm(ctx).(*forms.NewDiscordHookForm) | 	form := web.GetForm(ctx).(*forms.NewDiscordHookForm) | ||||||
| 	ctx.Data["Title"] = ctx.Tr("repo.settings") |  | ||||||
| 	ctx.Data["PageIsSettingsHooks"] = true |  | ||||||
| 	ctx.Data["PageIsSettingsHooksNew"] = true |  | ||||||
| 	ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook.HookEvent{}} |  | ||||||
| 	ctx.Data["HookType"] = webhook.DISCORD |  | ||||||
|  |  | ||||||
| 	orCtx, err := getOrgRepoCtx(ctx) | 	createWebhook(ctx, webhookCreationParams{ | ||||||
| 	if err != nil { | 		URL:         form.PayloadURL, | ||||||
| 		ctx.ServerError("getOrgRepoCtx", err) | 		ContentType: webhook.ContentTypeJSON, | ||||||
| 		return | 		WebhookForm: form.WebhookForm, | ||||||
| 	} | 		Type:        webhook.DISCORD, | ||||||
|  | 		Meta: &webhook_service.DiscordMeta{ | ||||||
| 	if ctx.HasError() { | 			Username: form.Username, | ||||||
| 		ctx.HTML(http.StatusOK, orCtx.NewTemplate) | 			IconURL:  form.IconURL, | ||||||
| 		return | 		}, | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	meta, err := json.Marshal(&webhook_service.DiscordMeta{ |  | ||||||
| 		Username: form.Username, |  | ||||||
| 		IconURL:  form.IconURL, |  | ||||||
| 	}) | 	}) | ||||||
| 	if err != nil { |  | ||||||
| 		ctx.ServerError("Marshal", err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	w := &webhook.Webhook{ |  | ||||||
| 		RepoID:          orCtx.RepoID, |  | ||||||
| 		URL:             form.PayloadURL, |  | ||||||
| 		ContentType:     webhook.ContentTypeJSON, |  | ||||||
| 		HookEvent:       ParseHookEvent(form.WebhookForm), |  | ||||||
| 		IsActive:        form.Active, |  | ||||||
| 		Type:            webhook.DISCORD, |  | ||||||
| 		Meta:            string(meta), |  | ||||||
| 		OrgID:           orCtx.OrgID, |  | ||||||
| 		IsSystemWebhook: orCtx.IsSystemWebhook, |  | ||||||
| 	} |  | ||||||
| 	if err := w.UpdateEvent(); err != nil { |  | ||||||
| 		ctx.ServerError("UpdateEvent", err) |  | ||||||
| 		return |  | ||||||
| 	} else if err := webhook.CreateWebhook(ctx, w); err != nil { |  | ||||||
| 		ctx.ServerError("CreateWebhook", err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success")) |  | ||||||
| 	ctx.Redirect(orCtx.Link) |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // DingtalkHooksNewPost response for creating dingtalk hook | // DingtalkHooksNewPost response for creating dingtalk hook | ||||||
| func DingtalkHooksNewPost(ctx *context.Context) { | func DingtalkHooksNewPost(ctx *context.Context) { | ||||||
| 	form := web.GetForm(ctx).(*forms.NewDingtalkHookForm) | 	form := web.GetForm(ctx).(*forms.NewDingtalkHookForm) | ||||||
| 	ctx.Data["Title"] = ctx.Tr("repo.settings") |  | ||||||
| 	ctx.Data["PageIsSettingsHooks"] = true |  | ||||||
| 	ctx.Data["PageIsSettingsHooksNew"] = true |  | ||||||
| 	ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook.HookEvent{}} |  | ||||||
| 	ctx.Data["HookType"] = webhook.DINGTALK |  | ||||||
|  |  | ||||||
| 	orCtx, err := getOrgRepoCtx(ctx) | 	createWebhook(ctx, webhookCreationParams{ | ||||||
| 	if err != nil { | 		URL:         form.PayloadURL, | ||||||
| 		ctx.ServerError("getOrgRepoCtx", err) | 		ContentType: webhook.ContentTypeJSON, | ||||||
| 		return | 		WebhookForm: form.WebhookForm, | ||||||
| 	} | 		Type:        webhook.DINGTALK, | ||||||
|  | 	}) | ||||||
| 	if ctx.HasError() { |  | ||||||
| 		ctx.HTML(http.StatusOK, orCtx.NewTemplate) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	w := &webhook.Webhook{ |  | ||||||
| 		RepoID:          orCtx.RepoID, |  | ||||||
| 		URL:             form.PayloadURL, |  | ||||||
| 		ContentType:     webhook.ContentTypeJSON, |  | ||||||
| 		HookEvent:       ParseHookEvent(form.WebhookForm), |  | ||||||
| 		IsActive:        form.Active, |  | ||||||
| 		Type:            webhook.DINGTALK, |  | ||||||
| 		Meta:            "", |  | ||||||
| 		OrgID:           orCtx.OrgID, |  | ||||||
| 		IsSystemWebhook: orCtx.IsSystemWebhook, |  | ||||||
| 	} |  | ||||||
| 	if err := w.UpdateEvent(); err != nil { |  | ||||||
| 		ctx.ServerError("UpdateEvent", err) |  | ||||||
| 		return |  | ||||||
| 	} else if err := webhook.CreateWebhook(ctx, w); err != nil { |  | ||||||
| 		ctx.ServerError("CreateWebhook", err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success")) |  | ||||||
| 	ctx.Redirect(orCtx.Link) |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // TelegramHooksNewPost response for creating telegram hook | // TelegramHooksNewPost response for creating telegram hook | ||||||
| func TelegramHooksNewPost(ctx *context.Context) { | func TelegramHooksNewPost(ctx *context.Context) { | ||||||
| 	form := web.GetForm(ctx).(*forms.NewTelegramHookForm) | 	form := web.GetForm(ctx).(*forms.NewTelegramHookForm) | ||||||
| 	ctx.Data["Title"] = ctx.Tr("repo.settings") |  | ||||||
| 	ctx.Data["PageIsSettingsHooks"] = true |  | ||||||
| 	ctx.Data["PageIsSettingsHooksNew"] = true |  | ||||||
| 	ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook.HookEvent{}} |  | ||||||
| 	ctx.Data["HookType"] = webhook.TELEGRAM |  | ||||||
|  |  | ||||||
| 	orCtx, err := getOrgRepoCtx(ctx) | 	createWebhook(ctx, webhookCreationParams{ | ||||||
| 	if err != nil { | 		URL:         fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage?chat_id=%s", url.PathEscape(form.BotToken), url.QueryEscape(form.ChatID)), | ||||||
| 		ctx.ServerError("getOrgRepoCtx", err) | 		ContentType: webhook.ContentTypeJSON, | ||||||
| 		return | 		WebhookForm: form.WebhookForm, | ||||||
| 	} | 		Type:        webhook.TELEGRAM, | ||||||
|  | 		Meta: &webhook_service.TelegramMeta{ | ||||||
| 	if ctx.HasError() { | 			BotToken: form.BotToken, | ||||||
| 		ctx.HTML(http.StatusOK, orCtx.NewTemplate) | 			ChatID:   form.ChatID, | ||||||
| 		return | 		}, | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	meta, err := json.Marshal(&webhook_service.TelegramMeta{ |  | ||||||
| 		BotToken: form.BotToken, |  | ||||||
| 		ChatID:   form.ChatID, |  | ||||||
| 	}) | 	}) | ||||||
| 	if err != nil { |  | ||||||
| 		ctx.ServerError("Marshal", err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	w := &webhook.Webhook{ |  | ||||||
| 		RepoID:          orCtx.RepoID, |  | ||||||
| 		URL:             fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage?chat_id=%s", url.PathEscape(form.BotToken), url.QueryEscape(form.ChatID)), |  | ||||||
| 		ContentType:     webhook.ContentTypeJSON, |  | ||||||
| 		HookEvent:       ParseHookEvent(form.WebhookForm), |  | ||||||
| 		IsActive:        form.Active, |  | ||||||
| 		Type:            webhook.TELEGRAM, |  | ||||||
| 		Meta:            string(meta), |  | ||||||
| 		OrgID:           orCtx.OrgID, |  | ||||||
| 		IsSystemWebhook: orCtx.IsSystemWebhook, |  | ||||||
| 	} |  | ||||||
| 	if err := w.UpdateEvent(); err != nil { |  | ||||||
| 		ctx.ServerError("UpdateEvent", err) |  | ||||||
| 		return |  | ||||||
| 	} else if err := webhook.CreateWebhook(ctx, w); err != nil { |  | ||||||
| 		ctx.ServerError("CreateWebhook", err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success")) |  | ||||||
| 	ctx.Redirect(orCtx.Link) |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // MatrixHooksNewPost response for creating a Matrix hook | // MatrixHooksNewPost response for creating a Matrix hook | ||||||
| func MatrixHooksNewPost(ctx *context.Context) { | func MatrixHooksNewPost(ctx *context.Context) { | ||||||
| 	form := web.GetForm(ctx).(*forms.NewMatrixHookForm) | 	form := web.GetForm(ctx).(*forms.NewMatrixHookForm) | ||||||
| 	ctx.Data["Title"] = ctx.Tr("repo.settings") |  | ||||||
| 	ctx.Data["PageIsSettingsHooks"] = true |  | ||||||
| 	ctx.Data["PageIsSettingsHooksNew"] = true |  | ||||||
| 	ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook.HookEvent{}} |  | ||||||
| 	ctx.Data["HookType"] = webhook.MATRIX |  | ||||||
|  |  | ||||||
| 	orCtx, err := getOrgRepoCtx(ctx) | 	createWebhook(ctx, webhookCreationParams{ | ||||||
| 	if err != nil { | 		URL:         fmt.Sprintf("%s/_matrix/client/r0/rooms/%s/send/m.room.message", form.HomeserverURL, url.PathEscape(form.RoomID)), | ||||||
| 		ctx.ServerError("getOrgRepoCtx", err) | 		ContentType: webhook.ContentTypeJSON, | ||||||
| 		return | 		HTTPMethod:  http.MethodPut, | ||||||
| 	} | 		WebhookForm: form.WebhookForm, | ||||||
|  | 		Type:        webhook.MATRIX, | ||||||
| 	if ctx.HasError() { | 		Meta: &webhook_service.MatrixMeta{ | ||||||
| 		ctx.HTML(http.StatusOK, orCtx.NewTemplate) | 			HomeserverURL: form.HomeserverURL, | ||||||
| 		return | 			Room:          form.RoomID, | ||||||
| 	} | 			AccessToken:   form.AccessToken, | ||||||
|  | 			MessageType:   form.MessageType, | ||||||
| 	meta, err := json.Marshal(&webhook_service.MatrixMeta{ | 		}, | ||||||
| 		HomeserverURL: form.HomeserverURL, |  | ||||||
| 		Room:          form.RoomID, |  | ||||||
| 		AccessToken:   form.AccessToken, |  | ||||||
| 		MessageType:   form.MessageType, |  | ||||||
| 	}) | 	}) | ||||||
| 	if err != nil { |  | ||||||
| 		ctx.ServerError("Marshal", err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	w := &webhook.Webhook{ |  | ||||||
| 		RepoID:          orCtx.RepoID, |  | ||||||
| 		URL:             fmt.Sprintf("%s/_matrix/client/r0/rooms/%s/send/m.room.message", form.HomeserverURL, url.PathEscape(form.RoomID)), |  | ||||||
| 		ContentType:     webhook.ContentTypeJSON, |  | ||||||
| 		HTTPMethod:      "PUT", |  | ||||||
| 		HookEvent:       ParseHookEvent(form.WebhookForm), |  | ||||||
| 		IsActive:        form.Active, |  | ||||||
| 		Type:            webhook.MATRIX, |  | ||||||
| 		Meta:            string(meta), |  | ||||||
| 		OrgID:           orCtx.OrgID, |  | ||||||
| 		IsSystemWebhook: orCtx.IsSystemWebhook, |  | ||||||
| 	} |  | ||||||
| 	if err := w.UpdateEvent(); err != nil { |  | ||||||
| 		ctx.ServerError("UpdateEvent", err) |  | ||||||
| 		return |  | ||||||
| 	} else if err := webhook.CreateWebhook(ctx, w); err != nil { |  | ||||||
| 		ctx.ServerError("CreateWebhook", err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success")) |  | ||||||
| 	ctx.Redirect(orCtx.Link) |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // MSTeamsHooksNewPost response for creating MS Teams hook | // MSTeamsHooksNewPost response for creating MS Teams hook | ||||||
| func MSTeamsHooksNewPost(ctx *context.Context) { | func MSTeamsHooksNewPost(ctx *context.Context) { | ||||||
| 	form := web.GetForm(ctx).(*forms.NewMSTeamsHookForm) | 	form := web.GetForm(ctx).(*forms.NewMSTeamsHookForm) | ||||||
| 	ctx.Data["Title"] = ctx.Tr("repo.settings") |  | ||||||
| 	ctx.Data["PageIsSettingsHooks"] = true |  | ||||||
| 	ctx.Data["PageIsSettingsHooksNew"] = true |  | ||||||
| 	ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook.HookEvent{}} |  | ||||||
| 	ctx.Data["HookType"] = webhook.MSTEAMS |  | ||||||
|  |  | ||||||
| 	orCtx, err := getOrgRepoCtx(ctx) | 	createWebhook(ctx, webhookCreationParams{ | ||||||
| 	if err != nil { | 		URL:         form.PayloadURL, | ||||||
| 		ctx.ServerError("getOrgRepoCtx", err) | 		ContentType: webhook.ContentTypeJSON, | ||||||
| 		return | 		WebhookForm: form.WebhookForm, | ||||||
| 	} | 		Type:        webhook.MSTEAMS, | ||||||
|  | 	}) | ||||||
| 	if ctx.HasError() { |  | ||||||
| 		ctx.HTML(http.StatusOK, orCtx.NewTemplate) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	w := &webhook.Webhook{ |  | ||||||
| 		RepoID:          orCtx.RepoID, |  | ||||||
| 		URL:             form.PayloadURL, |  | ||||||
| 		ContentType:     webhook.ContentTypeJSON, |  | ||||||
| 		HookEvent:       ParseHookEvent(form.WebhookForm), |  | ||||||
| 		IsActive:        form.Active, |  | ||||||
| 		Type:            webhook.MSTEAMS, |  | ||||||
| 		Meta:            "", |  | ||||||
| 		OrgID:           orCtx.OrgID, |  | ||||||
| 		IsSystemWebhook: orCtx.IsSystemWebhook, |  | ||||||
| 	} |  | ||||||
| 	if err := w.UpdateEvent(); err != nil { |  | ||||||
| 		ctx.ServerError("UpdateEvent", err) |  | ||||||
| 		return |  | ||||||
| 	} else if err := webhook.CreateWebhook(ctx, w); err != nil { |  | ||||||
| 		ctx.ServerError("CreateWebhook", err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success")) |  | ||||||
| 	ctx.Redirect(orCtx.Link) |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // SlackHooksNewPost response for creating slack hook | // SlackHooksNewPost response for creating slack hook | ||||||
| func SlackHooksNewPost(ctx *context.Context) { | func SlackHooksNewPost(ctx *context.Context) { | ||||||
| 	form := web.GetForm(ctx).(*forms.NewSlackHookForm) | 	form := web.GetForm(ctx).(*forms.NewSlackHookForm) | ||||||
| 	ctx.Data["Title"] = ctx.Tr("repo.settings") |  | ||||||
| 	ctx.Data["PageIsSettingsHooks"] = true |  | ||||||
| 	ctx.Data["PageIsSettingsHooksNew"] = true |  | ||||||
| 	ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook.HookEvent{}} |  | ||||||
| 	ctx.Data["HookType"] = webhook.SLACK |  | ||||||
|  |  | ||||||
| 	orCtx, err := getOrgRepoCtx(ctx) | 	createWebhook(ctx, webhookCreationParams{ | ||||||
| 	if err != nil { | 		URL:         form.PayloadURL, | ||||||
| 		ctx.ServerError("getOrgRepoCtx", err) | 		ContentType: webhook.ContentTypeJSON, | ||||||
| 		return | 		WebhookForm: form.WebhookForm, | ||||||
| 	} | 		Type:        webhook.SLACK, | ||||||
|  | 		Meta: &webhook_service.SlackMeta{ | ||||||
| 	if ctx.HasError() { | 			Channel:  strings.TrimSpace(form.Channel), | ||||||
| 		ctx.HTML(http.StatusOK, orCtx.NewTemplate) | 			Username: form.Username, | ||||||
| 		return | 			IconURL:  form.IconURL, | ||||||
| 	} | 			Color:    form.Color, | ||||||
|  | 		}, | ||||||
| 	if form.HasInvalidChannel() { |  | ||||||
| 		ctx.Flash.Error(ctx.Tr("repo.settings.add_webhook.invalid_channel_name")) |  | ||||||
| 		ctx.Redirect(orCtx.LinkNew + "/slack/new") |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	meta, err := json.Marshal(&webhook_service.SlackMeta{ |  | ||||||
| 		Channel:  strings.TrimSpace(form.Channel), |  | ||||||
| 		Username: form.Username, |  | ||||||
| 		IconURL:  form.IconURL, |  | ||||||
| 		Color:    form.Color, |  | ||||||
| 	}) | 	}) | ||||||
| 	if err != nil { |  | ||||||
| 		ctx.ServerError("Marshal", err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	w := &webhook.Webhook{ |  | ||||||
| 		RepoID:          orCtx.RepoID, |  | ||||||
| 		URL:             form.PayloadURL, |  | ||||||
| 		ContentType:     webhook.ContentTypeJSON, |  | ||||||
| 		HookEvent:       ParseHookEvent(form.WebhookForm), |  | ||||||
| 		IsActive:        form.Active, |  | ||||||
| 		Type:            webhook.SLACK, |  | ||||||
| 		Meta:            string(meta), |  | ||||||
| 		OrgID:           orCtx.OrgID, |  | ||||||
| 		IsSystemWebhook: orCtx.IsSystemWebhook, |  | ||||||
| 	} |  | ||||||
| 	if err := w.UpdateEvent(); err != nil { |  | ||||||
| 		ctx.ServerError("UpdateEvent", err) |  | ||||||
| 		return |  | ||||||
| 	} else if err := webhook.CreateWebhook(ctx, w); err != nil { |  | ||||||
| 		ctx.ServerError("CreateWebhook", err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success")) |  | ||||||
| 	ctx.Redirect(orCtx.Link) |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // FeishuHooksNewPost response for creating feishu hook | // FeishuHooksNewPost response for creating feishu hook | ||||||
| func FeishuHooksNewPost(ctx *context.Context) { | func FeishuHooksNewPost(ctx *context.Context) { | ||||||
| 	form := web.GetForm(ctx).(*forms.NewFeishuHookForm) | 	form := web.GetForm(ctx).(*forms.NewFeishuHookForm) | ||||||
| 	ctx.Data["Title"] = ctx.Tr("repo.settings") |  | ||||||
| 	ctx.Data["PageIsSettingsHooks"] = true |  | ||||||
| 	ctx.Data["PageIsSettingsHooksNew"] = true |  | ||||||
| 	ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook.HookEvent{}} |  | ||||||
| 	ctx.Data["HookType"] = webhook.FEISHU |  | ||||||
|  |  | ||||||
| 	orCtx, err := getOrgRepoCtx(ctx) | 	createWebhook(ctx, webhookCreationParams{ | ||||||
| 	if err != nil { | 		URL:         form.PayloadURL, | ||||||
| 		ctx.ServerError("getOrgRepoCtx", err) | 		ContentType: webhook.ContentTypeJSON, | ||||||
| 		return | 		WebhookForm: form.WebhookForm, | ||||||
| 	} | 		Type:        webhook.FEISHU, | ||||||
|  | 	}) | ||||||
| 	if ctx.HasError() { |  | ||||||
| 		ctx.HTML(http.StatusOK, orCtx.NewTemplate) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	w := &webhook.Webhook{ |  | ||||||
| 		RepoID:          orCtx.RepoID, |  | ||||||
| 		URL:             form.PayloadURL, |  | ||||||
| 		ContentType:     webhook.ContentTypeJSON, |  | ||||||
| 		HookEvent:       ParseHookEvent(form.WebhookForm), |  | ||||||
| 		IsActive:        form.Active, |  | ||||||
| 		Type:            webhook.FEISHU, |  | ||||||
| 		Meta:            "", |  | ||||||
| 		OrgID:           orCtx.OrgID, |  | ||||||
| 		IsSystemWebhook: orCtx.IsSystemWebhook, |  | ||||||
| 	} |  | ||||||
| 	if err := w.UpdateEvent(); err != nil { |  | ||||||
| 		ctx.ServerError("UpdateEvent", err) |  | ||||||
| 		return |  | ||||||
| 	} else if err := webhook.CreateWebhook(ctx, w); err != nil { |  | ||||||
| 		ctx.ServerError("CreateWebhook", err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success")) |  | ||||||
| 	ctx.Redirect(orCtx.Link) |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // WechatworkHooksNewPost response for creating wechatwork hook | // WechatworkHooksNewPost response for creating wechatwork hook | ||||||
| func WechatworkHooksNewPost(ctx *context.Context) { | func WechatworkHooksNewPost(ctx *context.Context) { | ||||||
| 	form := web.GetForm(ctx).(*forms.NewWechatWorkHookForm) | 	form := web.GetForm(ctx).(*forms.NewWechatWorkHookForm) | ||||||
|  |  | ||||||
| 	ctx.Data["Title"] = ctx.Tr("repo.settings") | 	createWebhook(ctx, webhookCreationParams{ | ||||||
| 	ctx.Data["PageIsSettingsHooks"] = true | 		URL:         form.PayloadURL, | ||||||
| 	ctx.Data["PageIsSettingsHooksNew"] = true | 		ContentType: webhook.ContentTypeJSON, | ||||||
| 	ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook.HookEvent{}} | 		WebhookForm: form.WebhookForm, | ||||||
| 	ctx.Data["HookType"] = webhook.WECHATWORK | 		Type:        webhook.WECHATWORK, | ||||||
|  | 	}) | ||||||
| 	orCtx, err := getOrgRepoCtx(ctx) |  | ||||||
| 	if err != nil { |  | ||||||
| 		ctx.ServerError("getOrgRepoCtx", err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if ctx.HasError() { |  | ||||||
| 		ctx.HTML(http.StatusOK, orCtx.NewTemplate) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	w := &webhook.Webhook{ |  | ||||||
| 		RepoID:          orCtx.RepoID, |  | ||||||
| 		URL:             form.PayloadURL, |  | ||||||
| 		ContentType:     webhook.ContentTypeJSON, |  | ||||||
| 		HookEvent:       ParseHookEvent(form.WebhookForm), |  | ||||||
| 		IsActive:        form.Active, |  | ||||||
| 		Type:            webhook.WECHATWORK, |  | ||||||
| 		Meta:            "", |  | ||||||
| 		OrgID:           orCtx.OrgID, |  | ||||||
| 		IsSystemWebhook: orCtx.IsSystemWebhook, |  | ||||||
| 	} |  | ||||||
| 	if err := w.UpdateEvent(); err != nil { |  | ||||||
| 		ctx.ServerError("UpdateEvent", err) |  | ||||||
| 		return |  | ||||||
| 	} else if err := webhook.CreateWebhook(ctx, w); err != nil { |  | ||||||
| 		ctx.ServerError("CreateWebhook", err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success")) |  | ||||||
| 	ctx.Redirect(orCtx.Link) |  | ||||||
| } | } | ||||||
|  |  | ||||||
| // PackagistHooksNewPost response for creating packagist hook | // PackagistHooksNewPost response for creating packagist hook | ||||||
| func PackagistHooksNewPost(ctx *context.Context) { | func PackagistHooksNewPost(ctx *context.Context) { | ||||||
| 	form := web.GetForm(ctx).(*forms.NewPackagistHookForm) | 	form := web.GetForm(ctx).(*forms.NewPackagistHookForm) | ||||||
| 	ctx.Data["Title"] = ctx.Tr("repo.settings") |  | ||||||
| 	ctx.Data["PageIsSettingsHooks"] = true |  | ||||||
| 	ctx.Data["PageIsSettingsHooksNew"] = true |  | ||||||
| 	ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook.HookEvent{}} |  | ||||||
| 	ctx.Data["HookType"] = webhook.PACKAGIST |  | ||||||
|  |  | ||||||
| 	orCtx, err := getOrgRepoCtx(ctx) | 	createWebhook(ctx, webhookCreationParams{ | ||||||
| 	if err != nil { | 		URL:         fmt.Sprintf("https://packagist.org/api/update-package?username=%s&apiToken=%s", url.QueryEscape(form.Username), url.QueryEscape(form.APIToken)), | ||||||
| 		ctx.ServerError("getOrgRepoCtx", err) | 		ContentType: webhook.ContentTypeJSON, | ||||||
| 		return | 		WebhookForm: form.WebhookForm, | ||||||
| 	} | 		Type:        webhook.PACKAGIST, | ||||||
|  | 		Meta: &webhook_service.PackagistMeta{ | ||||||
| 	if ctx.HasError() { | 			Username:   form.Username, | ||||||
| 		ctx.HTML(http.StatusOK, orCtx.NewTemplate) | 			APIToken:   form.APIToken, | ||||||
| 		return | 			PackageURL: form.PackageURL, | ||||||
| 	} | 		}, | ||||||
|  |  | ||||||
| 	meta, err := json.Marshal(&webhook_service.PackagistMeta{ |  | ||||||
| 		Username:   form.Username, |  | ||||||
| 		APIToken:   form.APIToken, |  | ||||||
| 		PackageURL: form.PackageURL, |  | ||||||
| 	}) | 	}) | ||||||
| 	if err != nil { |  | ||||||
| 		ctx.ServerError("Marshal", err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	w := &webhook.Webhook{ |  | ||||||
| 		RepoID:          orCtx.RepoID, |  | ||||||
| 		URL:             fmt.Sprintf("https://packagist.org/api/update-package?username=%s&apiToken=%s", url.QueryEscape(form.Username), url.QueryEscape(form.APIToken)), |  | ||||||
| 		ContentType:     webhook.ContentTypeJSON, |  | ||||||
| 		HookEvent:       ParseHookEvent(form.WebhookForm), |  | ||||||
| 		IsActive:        form.Active, |  | ||||||
| 		Type:            webhook.PACKAGIST, |  | ||||||
| 		Meta:            string(meta), |  | ||||||
| 		OrgID:           orCtx.OrgID, |  | ||||||
| 		IsSystemWebhook: orCtx.IsSystemWebhook, |  | ||||||
| 	} |  | ||||||
| 	if err := w.UpdateEvent(); err != nil { |  | ||||||
| 		ctx.ServerError("UpdateEvent", err) |  | ||||||
| 		return |  | ||||||
| 	} else if err := webhook.CreateWebhook(ctx, w); err != nil { |  | ||||||
| 		ctx.ServerError("CreateWebhook", err) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success")) |  | ||||||
| 	ctx.Redirect(orCtx.Link) |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func checkWebhook(ctx *context.Context) (*orgRepoCtx, *webhook.Webhook) { | func checkWebhook(ctx *context.Context) (*orgRepoCtx, *webhook.Webhook) { | ||||||
| @@ -894,12 +579,6 @@ func SlackHooksEditPost(ctx *context.Context) { | |||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if form.HasInvalidChannel() { |  | ||||||
| 		ctx.Flash.Error(ctx.Tr("repo.settings.add_webhook.invalid_channel_name")) |  | ||||||
| 		ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID)) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	meta, err := json.Marshal(&webhook_service.SlackMeta{ | 	meta, err := json.Marshal(&webhook_service.SlackMeta{ | ||||||
| 		Channel:  strings.TrimSpace(form.Channel), | 		Channel:  strings.TrimSpace(form.Channel), | ||||||
| 		Username: form.Username, | 		Username: form.Username, | ||||||
|   | |||||||
| @@ -17,7 +17,7 @@ import ( | |||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
| 	"code.gitea.io/gitea/modules/structs" | 	"code.gitea.io/gitea/modules/structs" | ||||||
| 	"code.gitea.io/gitea/modules/web/middleware" | 	"code.gitea.io/gitea/modules/web/middleware" | ||||||
| 	"code.gitea.io/gitea/routers/utils" | 	"code.gitea.io/gitea/services/webhook" | ||||||
|  |  | ||||||
| 	"gitea.com/go-chi/binding" | 	"gitea.com/go-chi/binding" | ||||||
| ) | ) | ||||||
| @@ -305,14 +305,16 @@ type NewSlackHookForm struct { | |||||||
| // Validate validates the fields | // Validate validates the fields | ||||||
| func (f *NewSlackHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { | func (f *NewSlackHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { | ||||||
| 	ctx := context.GetContext(req) | 	ctx := context.GetContext(req) | ||||||
|  | 	if !webhook.IsValidSlackChannel(strings.TrimSpace(f.Channel)) { | ||||||
|  | 		errs = append(errs, binding.Error{ | ||||||
|  | 			FieldNames:     []string{"Channel"}, | ||||||
|  | 			Classification: "", | ||||||
|  | 			Message:        ctx.Tr("repo.settings.add_webhook.invalid_channel_name"), | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
| 	return middleware.Validate(errs, ctx.Data, f, ctx.Locale) | 	return middleware.Validate(errs, ctx.Data, f, ctx.Locale) | ||||||
| } | } | ||||||
|  |  | ||||||
| // HasInvalidChannel validates the channel name is in the right format |  | ||||||
| func (f NewSlackHookForm) HasInvalidChannel() bool { |  | ||||||
| 	return !utils.IsValidSlackChannel(f.Channel) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // NewDiscordHookForm form for creating discord hook | // NewDiscordHookForm form for creating discord hook | ||||||
| type NewDiscordHookForm struct { | type NewDiscordHookForm struct { | ||||||
| 	PayloadURL string `binding:"Required;ValidUrl"` | 	PayloadURL string `binding:"Required;ValidUrl"` | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ package webhook | |||||||
| import ( | import ( | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"regexp" | ||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
| 	webhook_model "code.gitea.io/gitea/models/webhook" | 	webhook_model "code.gitea.io/gitea/models/webhook" | ||||||
| @@ -286,3 +287,13 @@ func GetSlackPayload(p api.Payloader, event webhook_model.HookEventType, meta st | |||||||
|  |  | ||||||
| 	return convertPayloader(s, p, event) | 	return convertPayloader(s, p, event) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | var slackChannel = regexp.MustCompile(`^#?[a-z0-9_-]{1,80}$`) | ||||||
|  |  | ||||||
|  | // IsValidSlackChannel validates a channel name conforms to what slack expects: | ||||||
|  | // https://api.slack.com/methods/conversations.rename#naming | ||||||
|  | // Conversation names can only contain lowercase letters, numbers, hyphens, and underscores, and must be 80 characters or less. | ||||||
|  | // Gitea accepts if it starts with a #. | ||||||
|  | func IsValidSlackChannel(name string) bool { | ||||||
|  | 	return slackChannel.MatchString(name) | ||||||
|  | } | ||||||
|   | |||||||
| @@ -170,3 +170,22 @@ func TestSlackJSONPayload(t *testing.T) { | |||||||
| 	require.NoError(t, err) | 	require.NoError(t, err) | ||||||
| 	assert.NotEmpty(t, json) | 	assert.NotEmpty(t, json) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func TestIsValidSlackChannel(t *testing.T) { | ||||||
|  | 	tt := []struct { | ||||||
|  | 		channelName string | ||||||
|  | 		expected    bool | ||||||
|  | 	}{ | ||||||
|  | 		{"gitea", true}, | ||||||
|  | 		{"#gitea", true}, | ||||||
|  | 		{"  ", false}, | ||||||
|  | 		{"#", false}, | ||||||
|  | 		{" #", false}, | ||||||
|  | 		{"gitea   ", false}, | ||||||
|  | 		{"  gitea", false}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, v := range tt { | ||||||
|  | 		assert.Equal(t, v.expected, IsValidSlackChannel(v.channelName)) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 oliverpool
					oliverpool