From 9c915e4e1a915d9f74a8766b48d8baa051abaf6c Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Mon, 10 Aug 2026 09:25:31 +0800 Subject: [PATCH] refactor: form binding validation (#38832) Make "form-fetch-action" highlight the invalid field as "error" --- modules/validation/binding.go | 19 +- modules/web/middleware/binding.go | 221 ++++++++++-------- modules/web/middleware/binding_test.go | 31 +++ modules/web/router.go | 6 +- options/locale/locale_en-US.json | 37 ++- routers/web/admin/auths.go | 4 +- routers/web/repo/setting/deploy_key.go | 53 ++--- routers/web/repo/setting/settings_test.go | 63 ++--- routers/web/web.go | 2 +- services/context/context.go | 63 +++-- services/contexttest/context_tests.go | 6 + services/forms/admin.go | 10 +- services/forms/auth_form.go | 2 +- services/forms/org.go | 6 +- services/forms/package_form.go | 2 +- services/forms/repo_branch_form.go | 4 +- services/forms/repo_form.go | 68 +++--- services/forms/repo_form_editor.go | 2 +- services/forms/repo_tag_form.go | 2 +- services/forms/runner.go | 2 +- services/forms/user_form.go | 52 ++--- services/forms/user_form_auth_openid.go | 6 +- templates/repo/settings/deploy_keys.tmpl | 10 +- tests/integration/repo_branch_test.go | 4 +- tests/integration/user_test.go | 2 +- .../js/features/common-fetch-action.test.ts | 16 +- web_src/js/features/common-fetch-action.ts | 41 +++- 27 files changed, 411 insertions(+), 323 deletions(-) create mode 100644 modules/web/middleware/binding_test.go diff --git a/modules/validation/binding.go b/modules/validation/binding.go index 373e1b4f29b..b4d2d9d1577 100644 --- a/modules/validation/binding.go +++ b/modules/validation/binding.go @@ -18,18 +18,13 @@ import ( ) const ( - // ErrGitRefName is git reference name error - ErrGitRefName = "GitRefNameError" - // ErrGlobPattern is returned when glob pattern is invalid - ErrGlobPattern = "GlobPattern" - // ErrRegexPattern is returned when a regex pattern is invalid - ErrRegexPattern = "RegexPattern" - // ErrUsername is username error - ErrUsername = "UsernameError" - // ErrInvalidGroupTeamMap is returned when a group team mapping is invalid + ErrCustomMessage = "CustomMessage" + ErrGitRefName = "GitRefNameError" + ErrGlobPattern = "GlobPattern" + ErrRegexPattern = "RegexPattern" + ErrUsername = "UsernameError" ErrInvalidGroupTeamMap = "InvalidGroupTeamMap" - // ErrInvalidBadgeSlug is returned when a badge slug is invalid - ErrInvalidBadgeSlug = "InvalidBadgeSlug" + ErrInvalidBadgeSlug = "InvalidBadgeSlug" ) type jsonProvider struct{} @@ -61,7 +56,7 @@ func AddBindingRules() { } func addGitRefNameBindingRule() { - // Git refname validation rule + // Git ref name validation rule binding.AddRule(&binding.Rule{ IsMatch: func(rule string) bool { return rule == "GitRefName" diff --git a/modules/web/middleware/binding.go b/modules/web/middleware/binding.go index 19a9d8bab92..d189c0e088c 100644 --- a/modules/web/middleware/binding.go +++ b/modules/web/middleware/binding.go @@ -5,9 +5,12 @@ package middleware import ( + "net/http" "reflect" "strings" + "gitea.dev/modules/reqctx" + "gitea.dev/modules/setting" "gitea.dev/modules/translation" "gitea.dev/modules/util" "gitea.dev/modules/validation" @@ -15,6 +18,14 @@ import ( "gitea.com/go-chi/binding" ) +// ValidateContext is a special context for form validation middleware. It may be different from other contexts. +type ValidateContext struct { + Locale translation.Locale + Data reqctx.ContextData + Req *http.Request + Resp http.ResponseWriter +} + // Form form binding interface type Form interface { binding.Validator @@ -49,7 +60,8 @@ func AssignForm(form any, data map[string]any) { } } -func getRuleBody(field reflect.StructField, prefix string) string { +func getRuleBody(field reflect.StructField, ruleName string) string { + prefix := ruleName + "(" for rule := range strings.SplitSeq(field.Tag.Get("binding"), ";") { if strings.HasPrefix(rule, prefix) { return rule[len(prefix) : len(rule)-1] @@ -58,117 +70,134 @@ func getRuleBody(field reflect.StructField, prefix string) string { return "" } -// GetSize get size int form tag -func GetSize(field reflect.StructField) string { - return getRuleBody(field, "Size(") -} - -// GetMinSize get minimal size in form tag -func GetMinSize(field reflect.StructField) string { - return getRuleBody(field, "MinSize(") -} - -// GetMaxSize get max size in form tag -func GetMaxSize(field reflect.StructField) string { - return getRuleBody(field, "MaxSize(") -} - -// GetInclude get include in form tag -func GetInclude(field reflect.StructField) string { - return getRuleBody(field, "Include(") -} - -func ReportValidationError(errs binding.Errors, data map[string]any, fieldName, classification, errorMsg string) binding.Errors { - errs.Add([]string{fieldName}, classification, errorMsg) - - data["HasError"] = true - data["ErrorMsg"] = fieldName + ": " + errorMsg - data["Err_"+fieldName] = true - // there is already a reported validation error, so no need to generate default error messages in Validate() - data["HasErrorFormValidation"] = true +func AddValidationError(errs binding.Errors, fieldName, errorMsg string) binding.Errors { + errs.Add([]string{fieldName}, validation.ErrCustomMessage, errorMsg) return errs } -func Validate(errs binding.Errors, data map[string]any, f Form, l translation.Locale) binding.Errors { - // try to restore the form's values as much as possible, - // especially for RenderWithErrDeprecated to re-render the form with errors - AssignForm(f, data) - - if errs.Len() == 0 || data["HasErrorFormValidation"] == true { - return errs +func getFieldDisplayNameForMessage(f Form, l translation.Locale, fieldNames []string) (field reflect.StructField, ok bool, displayName string) { + if len(fieldNames) == 0 { + return field, false, "" } - - // if HasError=true, then must set default error message - // because still a lot of places use `ctx.Data["ErrorMsg"].(string)` even if the error fields can't be found - data["HasError"] = true - data["ErrorMsg"] = l.TrString("form.unknown_error") - typ := reflect.TypeOf(f) if typ.Kind() == reflect.Pointer { typ = typ.Elem() } - field, fieldExists := typ.FieldByName(errs[0].FieldNames[0]) + field, fieldExists := typ.FieldByName(fieldNames[0]) if !fieldExists { - return errs + return field, false, "" } if field.Tag.Get("form") == "-" { + return field, false, "" + } + + trKeyFallback := "form." + field.Name + trKey := util.IfZero(field.Tag.Get("locale"), trKeyFallback) + displayName = l.TrString(trKey) + if displayName == trKeyFallback { + displayName = field.Name + } + return field, true, displayName +} + +func BuildValidationErrorForUser(f Form, l translation.Locale, bindingErrs binding.Errors) (errorMessage, errorFieldName string, fieldNames []string) { + if bindingErrs.Len() == 0 { + return "", "", nil + } + bindingErr := bindingErrs[0] + fieldNames, classification, bindingErrMsg := bindingErr.FieldNames, bindingErr.Classification, bindingErr.Message + field, ok, fieldDisplayName := getFieldDisplayNameForMessage(f, l, fieldNames) + if !ok { + return l.TrString("error.occurred"), "", fieldNames + } + + errorFieldName = field.Name + switch classification { + case binding.ERR_REQUIRED: + errorMessage = l.TrString("form.require_error", fieldDisplayName) + case binding.ERR_ALPHA_DASH: + errorMessage = l.TrString("form.alpha_dash_error", fieldDisplayName) + case binding.ERR_ALPHA_DASH_DOT: + errorMessage = l.TrString("form.alpha_dash_dot_error", fieldDisplayName) + case binding.ERR_MIN_SIZE: + errorMessage = l.TrString("form.min_size_error", fieldDisplayName, getRuleBody(field, "MinSize")) + case binding.ERR_MAX_SIZE: + errorMessage = l.TrString("form.max_size_error", fieldDisplayName, getRuleBody(field, "MaxSize")) + case binding.ERR_RANGE: + rangeMin, rangeMax, _ := strings.Cut(getRuleBody(field, "Range"), ",") + errorMessage = l.TrString("form.range_error", fieldDisplayName, rangeMin, rangeMax) + case binding.ERR_EMAIL: + errorMessage = l.TrString("form.email_error", fieldDisplayName) + case binding.ERR_URL: + errorMessage = l.TrString("form.url_error", fieldDisplayName) + case binding.ERR_IN: + ruleBody := getRuleBody(field, "In") + if strings.HasPrefix(ruleBody, ",") { + ruleBody = "(empty)" + ruleBody + } + errorMessage = l.TrString("form.in_error", fieldDisplayName, ruleBody) + case binding.ERR_INCLUDE: + errorMessage = l.TrString("form.include_error", fieldDisplayName, getRuleBody(field, "Include")) + + case validation.ErrCustomMessage: + errorMessage = bindingErrMsg + case validation.ErrGitRefName: + errorMessage = l.TrString("form.git_ref_name_error", fieldDisplayName) + case validation.ErrGlobPattern: + errorMessage = l.TrString("form.glob_pattern_error", fieldDisplayName, bindingErrMsg) + case validation.ErrRegexPattern: + errorMessage = l.TrString("form.regex_pattern_error", fieldDisplayName, bindingErrMsg) + case validation.ErrUsername: + errorMessage = l.TrString("form.username_error", fieldDisplayName) + case validation.ErrInvalidGroupTeamMap: + errorMessage = l.TrString("form.invalid_group_team_map_error", fieldDisplayName, bindingErrMsg) + case validation.ErrInvalidBadgeSlug: + errorMessage = l.TrString("form.invalid_slug_error", fieldDisplayName) + default: + setting.PanicInDevOrTesting("unknown binding error classification: %v", classification) + var msg string + if classification != "" && bindingErrMsg != "" { + msg = classification + ": " + bindingErrMsg + } else { + msg = util.IfZero(bindingErrMsg, classification) + if msg == "" { + setting.PanicInDevOrTesting("no error message for binding error: %v", bindingErr) + } + msg = util.IfZero(msg, "unknown error") + } + errorMessage = l.TrString("form.field_invalid_message", fieldDisplayName, msg) + } + return errorMessage, errorFieldName, fieldNames +} + +type contextKeySkipTmplFormValidationErrorType struct{} + +var contextKeySkipTmplFormValidationError contextKeySkipTmplFormValidationErrorType + +func SkipTmplFormValidationError(ctx reqctx.RequestContext) { + ctx.SetContextValue(contextKeySkipTmplFormValidationError, true) +} + +func Validate(ctx *ValidateContext, errs binding.Errors, f Form) binding.Errors { + if ctx.Req.Context().Value(contextKeySkipTmplFormValidationError) == true { + // if it is not using tmpl-based validation error handling, just return the errors + // for example: when using "form-fetch-action", the validation error can be handled by GetFetchActionForm + return errs + } + errorMessage, errorFieldName, _ := BuildValidationErrorForUser(f, ctx.Locale, errs) + if errorMessage == "" { return errs } - data["Err_"+field.Name] = true - - trName := field.Tag.Get("locale") - if len(trName) == 0 { - trName = l.TrString("form." + field.Name) - } else { - trName = l.TrString(trName) + // Legacy template error handling: try to restore the form's values as much as possible, + // especially for RenderWithErrDeprecated to re-render the form with errors. + AssignForm(f, ctx.Data) + ctx.Data["HasError"] = true + ctx.Data["ErrorMsg"] = errorMessage + if errorFieldName != "" { + ctx.Data["Err_"+errorFieldName] = true } - - switch errs[0].Classification { - case binding.ERR_REQUIRED: - data["ErrorMsg"] = trName + l.TrString("form.require_error") - case binding.ERR_ALPHA_DASH: - data["ErrorMsg"] = trName + l.TrString("form.alpha_dash_error") - case binding.ERR_ALPHA_DASH_DOT: - data["ErrorMsg"] = trName + l.TrString("form.alpha_dash_dot_error") - case validation.ErrGitRefName: - data["ErrorMsg"] = trName + l.TrString("form.git_ref_name_error") - case binding.ERR_SIZE: - data["ErrorMsg"] = trName + l.TrString("form.size_error", GetSize(field)) - case binding.ERR_MIN_SIZE: - data["ErrorMsg"] = trName + l.TrString("form.min_size_error", GetMinSize(field)) - case binding.ERR_MAX_SIZE: - data["ErrorMsg"] = trName + l.TrString("form.max_size_error", GetMaxSize(field)) - case binding.ERR_EMAIL: - data["ErrorMsg"] = trName + l.TrString("form.email_error") - case binding.ERR_URL: - data["ErrorMsg"] = trName + l.TrString("form.url_error", errs[0].Message) - case binding.ERR_INCLUDE: - data["ErrorMsg"] = trName + l.TrString("form.include_error", GetInclude(field)) - case validation.ErrGlobPattern: - data["ErrorMsg"] = trName + l.TrString("form.glob_pattern_error", errs[0].Message) - case validation.ErrRegexPattern: - data["ErrorMsg"] = trName + l.TrString("form.regex_pattern_error", errs[0].Message) - case validation.ErrUsername: - data["ErrorMsg"] = trName + l.TrString("form.username_error") - case validation.ErrInvalidGroupTeamMap: - data["ErrorMsg"] = trName + l.TrString("form.invalid_group_team_map_error", errs[0].Message) - case validation.ErrInvalidBadgeSlug: - data["ErrorMsg"] = trName + l.TrString("form.invalid_slug_error") - default: - msg := errs[0].Classification - if msg != "" && errs[0].Message != "" { - msg += ": " - } - - msg += errs[0].Message - if msg == "" { - msg = l.TrString("form.unknown_error") - } - data["ErrorMsg"] = trName + ": " + msg - } - return errs } diff --git a/modules/web/middleware/binding_test.go b/modules/web/middleware/binding_test.go new file mode 100644 index 00000000000..d3920254277 --- /dev/null +++ b/modules/web/middleware/binding_test.go @@ -0,0 +1,31 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package middleware + +import ( + "net/http" + "net/http/httptest" + "testing" + + "gitea.dev/modules/translation" + + "gitea.com/go-chi/binding" + "github.com/stretchr/testify/assert" +) + +type testRangeForm struct { + Hours int `binding:"Range(0,1000)"` +} + +func (f *testRangeForm) Validate(_ *http.Request, errs binding.Errors) binding.Errors { return errs } + +func TestBuildValidationErrorForUser(t *testing.T) { + // an out-of-range value must reach its own message instead of the panicking "default" branch + form := &testRangeForm{Hours: 2000} + errs := binding.Validate(httptest.NewRequest(http.MethodPost, "/", nil), form) + errorMessage, errorFieldName, fieldNames := BuildValidationErrorForUser(form, translation.MockLocale{}, errs) + assert.Equal(t, "form.range_error:Hours,0,1000", errorMessage) + assert.Equal(t, "Hours", errorFieldName) + assert.Equal(t, []string{"Hours"}, fieldNames) +} diff --git a/modules/web/router.go b/modules/web/router.go index 6541cb1f7b3..1b9ab534561 100644 --- a/modules/web/router.go +++ b/modules/web/router.go @@ -26,7 +26,7 @@ func Bind[T any](_ T) http.HandlerFunc { return func(resp http.ResponseWriter, req *http.Request) { theObj := new(T) // create a new form obj for every request but not use obj directly data := middleware.GetContextData(req.Context()) - binding.Bind(req, theObj) + _ = binding.Bind(req, theObj) // no need to handle "errs" here, the errors are handled in our middleware.Validate (binding.go) SetForm(data, theObj) middleware.AssignForm(theObj, data) } @@ -37,6 +37,10 @@ func SetForm(dataStore reqctx.ContextDataProvider, obj any) { dataStore.GetData()["__form"] = obj } +func IsFormSet(dataStore reqctx.RequestDataStore) bool { + return dataStore.GetData()["__form"] != nil +} + // GetForm returns the validate form information func GetForm[T any](dataStore reqctx.RequestDataStore) T { form, ok := dataStore.GetData()["__form"].(T) diff --git a/options/locale/locale_en-US.json b/options/locale/locale_en-US.json index c6725ca18a8..25640be60b7 100644 --- a/options/locale/locale_en-US.json +++ b/options/locale/locale_en-US.json @@ -523,9 +523,6 @@ "form.RepoName": "Repository name", "form.Email": "Email address", "form.Password": "Password", - "form.Retype": "Confirm Password", - "form.SSHTitle": "SSH key name", - "form.HttpsUrl": "HTTPS URL", "form.PayloadUrl": "Payload URL", "form.TeamName": "Team name", "form.AuthName": "Authorization name", @@ -538,22 +535,24 @@ "form.Content": "Content", "form.SSPISeparatorReplacement": "Separator", "form.SSPIDefaultLanguage": "Default Language", - "form.require_error": " cannot be empty.", - "form.alpha_dash_error": " should contain only alphanumeric, dash ('-') and underscore ('_') characters.", - "form.alpha_dash_dot_error": " should contain only alphanumeric, dash ('-'), underscore ('_') and dot ('.') characters.", - "form.git_ref_name_error": " must be a well-formed Git reference name.", - "form.size_error": " must be size %s.", - "form.min_size_error": " must contain at least %s characters.", - "form.max_size_error": " must contain at most %s characters.", - "form.email_error": " is not a valid email address.", - "form.url_error": "\"%s\" is not a valid URL.", - "form.include_error": " must contain substring \"%s\".", - "form.glob_pattern_error": " glob pattern is invalid: %s.", - "form.regex_pattern_error": " regex pattern is invalid: %s.", - "form.username_error": " can only contain alphanumeric characters ('0-9','a-z','A-Z'), dash ('-'), underscore ('_') and dot ('.'). It cannot begin or end with non-alphanumeric characters, and consecutive non-alphanumeric characters are also forbidden.", - "form.invalid_slug_error": " is invalid.", - "form.invalid_group_team_map_error": " mapping is invalid: %s", - "form.unknown_error": "Unknown error:", + "form.require_error": "%s cannot be empty.", + "form.alpha_dash_error": "%s should contain only alphanumeric, dash ('-') and underscore ('_') characters.", + "form.alpha_dash_dot_error": "%s should contain only alphanumeric, dash ('-'), underscore ('_') and dot ('.') characters.", + "form.git_ref_name_error": "%s must be a well-formed Git reference name.", + "form.size_error": "%[1]s must be size %[2]s.", + "form.min_size_error": "%[1]s must contain at least %[2]s characters.", + "form.max_size_error": "%[1]s must contain at most %[2]s characters.", + "form.range_error": "%[1]s must be a number from %[2]s to %[3]s.", + "form.email_error": "%s is not a valid email address.", + "form.url_error": "%s is not a valid URL.", + "form.in_error": "%[1]s must be one of the following: %[2]s.", + "form.include_error": "%[1]s must contain substring \"%[2]s\".", + "form.glob_pattern_error": "%[1]s glob pattern is invalid: %[2]s.", + "form.regex_pattern_error": "%[1]s regex pattern is invalid: %[2]s.", + "form.username_error": "%s can only contain alphanumeric characters ('0-9','a-z','A-Z'), dash ('-'), underscore ('_') and dot ('.'). It cannot begin or end with non-alphanumeric characters, and consecutive non-alphanumeric characters are also forbidden.", + "form.invalid_slug_error": "%s is invalid.", + "form.invalid_group_team_map_error": "%[1]s mapping is invalid: %[2]s", + "form.field_invalid_message": "%[1]s is invalid: %[2]s", "form.captcha_incorrect": "The CAPTCHA code is incorrect.", "form.password_not_match": "The passwords do not match.", "form.lang_select_error": "Select a language from the list.", diff --git a/routers/web/admin/auths.go b/routers/web/admin/auths.go index 2723ba102c2..5eef4a54062 100644 --- a/routers/web/admin/auths.go +++ b/routers/web/admin/auths.go @@ -212,11 +212,11 @@ func parseOAuth2Config(form forms.AuthenticationForm) *oauth2.Source { func parseSSPIConfig(ctx *context.Context, form forms.AuthenticationForm) (*sspi.Source, error) { if util.IsEmptyString(form.SSPISeparatorReplacement) { ctx.Data["Err_SSPISeparatorReplacement"] = true - return nil, errors.New(ctx.Locale.TrString("form.SSPISeparatorReplacement") + ctx.Locale.TrString("form.require_error")) + return nil, errors.New(ctx.Locale.TrString("form.require_error", ctx.Locale.TrString("form.SSPISeparatorReplacement"))) } if separatorAntiPattern.MatchString(form.SSPISeparatorReplacement) { ctx.Data["Err_SSPISeparatorReplacement"] = true - return nil, errors.New(ctx.Locale.TrString("form.SSPISeparatorReplacement") + ctx.Locale.TrString("form.alpha_dash_dot_error")) + return nil, errors.New(ctx.Locale.TrString("form.alpha_dash_dot_error", ctx.Locale.TrString("form.SSPISeparatorReplacement"))) } if form.SSPIDefaultLanguage != "" && !langCodePattern.MatchString(form.SSPIDefaultLanguage) { diff --git a/routers/web/repo/setting/deploy_key.go b/routers/web/repo/setting/deploy_key.go index ad398f6a691..5909cdc1003 100644 --- a/routers/web/repo/setting/deploy_key.go +++ b/routers/web/repo/setting/deploy_key.go @@ -4,13 +4,12 @@ package setting import ( + "errors" "net/http" asymkey_model "gitea.dev/models/asymkey" "gitea.dev/models/db" - "gitea.dev/modules/log" "gitea.dev/modules/setting" - "gitea.dev/modules/web" asymkey_service "gitea.dev/services/asymkey" "gitea.dev/services/context" "gitea.dev/services/forms" @@ -32,69 +31,45 @@ func DeployKeys(ctx *context.Context) { ctx.HTML(http.StatusOK, tplDeployKeys) } -// DeployKeysPost response for adding a deploy key of a repository +// DeployKeysPost response for adding a deploy-key of a repository func DeployKeysPost(ctx *context.Context) { - form := web.GetForm[*forms.AddKeyForm](ctx) - ctx.Data["Title"] = ctx.Tr("repo.settings.deploy_keys") - ctx.Data["PageIsSettingsKeys"] = true - ctx.Data["DisableSSH"] = setting.SSH.Disabled - - keys, err := db.Find[asymkey_model.DeployKey](ctx, asymkey_model.ListDeployKeysOptions{RepoID: ctx.Repo.Repository.ID}) - if err != nil { - ctx.ServerError("ListDeployKeys", err) + form := context.GetFetchActionForm[*forms.AddKeyForm](ctx) + if form == nil { return } - ctx.Data["Deploykeys"] = keys - - if ctx.HasError() { - ctx.HTML(http.StatusOK, tplDeployKeys) - return - } - content, err := asymkey_model.CheckPublicKeyString(form.Content) if err != nil { if db.IsErrSSHDisabled(err) { - ctx.Flash.Info(ctx.Tr("settings.ssh_disabled")) + ctx.JSONError(ctx.Tr("settings.ssh_disabled")) } else if asymkey_model.IsErrKeyUnableVerify(err) { - ctx.Flash.Info(ctx.Tr("form.unable_verify_ssh_key")) - } else if err == asymkey_model.ErrKeyIsPrivate { - ctx.Data["HasError"] = true - ctx.Data["Err_Content"] = true - ctx.Flash.Error(ctx.Tr("form.must_use_public_key")) + ctx.JSONErrorWithField(ctx.Tr("form.unable_verify_ssh_key"), "content") + } else if errors.Is(err, asymkey_model.ErrKeyIsPrivate) { + ctx.JSONErrorWithField(ctx.Tr("form.must_use_public_key"), "content") } else { - ctx.Data["HasError"] = true - ctx.Data["Err_Content"] = true - ctx.Flash.Error(ctx.Tr("form.invalid_ssh_key", err.Error())) + ctx.JSONErrorWithField(ctx.Tr("form.invalid_ssh_key", err.Error()), "content") } - ctx.Redirect(ctx.Repo.RepoLink + "/settings/keys") return } key, err := asymkey_model.AddDeployKey(ctx, ctx.Repo.Repository.ID, form.Title, content, !form.IsWritable) if err != nil { - ctx.Data["HasError"] = true switch { case asymkey_model.IsErrDeployKeyAlreadyExist(err): - ctx.Data["Err_Content"] = true - ctx.RenderWithErrDeprecated(ctx.Tr("repo.settings.key_been_used"), tplDeployKeys, &form) + ctx.JSONErrorWithField(ctx.Tr("repo.settings.key_been_used"), "content") case asymkey_model.IsErrKeyAlreadyExist(err): - ctx.Data["Err_Content"] = true - ctx.RenderWithErrDeprecated(ctx.Tr("settings.ssh_key_been_used"), tplDeployKeys, &form) + ctx.JSONErrorWithField(ctx.Tr("settings.ssh_key_been_used"), "content") case asymkey_model.IsErrKeyNameAlreadyUsed(err): - ctx.Data["Err_Title"] = true - ctx.RenderWithErrDeprecated(ctx.Tr("repo.settings.key_name_used"), tplDeployKeys, &form) + ctx.JSONErrorWithField(ctx.Tr("repo.settings.key_name_used"), "title") case asymkey_model.IsErrDeployKeyNameAlreadyUsed(err): - ctx.Data["Err_Title"] = true - ctx.RenderWithErrDeprecated(ctx.Tr("repo.settings.key_name_used"), tplDeployKeys, &form) + ctx.JSONErrorWithField(ctx.Tr("repo.settings.key_name_used"), "title") default: ctx.ServerError("AddDeployKey", err) } return } - log.Trace("Deploy key added: %d", ctx.Repo.Repository.ID) ctx.Flash.Success(ctx.Tr("repo.settings.add_key_success", key.Name)) - ctx.Redirect(ctx.Repo.RepoLink + "/settings/keys") + ctx.JSONRedirect(ctx.Repo.RepoLink + "/settings/keys") } // DeleteDeployKey response for deleting a deploy key diff --git a/routers/web/repo/setting/settings_test.go b/routers/web/repo/setting/settings_test.go index 68d7ab1d12f..3beecf7b6f3 100644 --- a/routers/web/repo/setting/settings_test.go +++ b/routers/web/repo/setting/settings_test.go @@ -5,6 +5,7 @@ package setting import ( "net/http" + "net/url" "testing" asymkey_model "gitea.dev/models/asymkey" @@ -27,53 +28,25 @@ import ( "github.com/stretchr/testify/require" ) -func TestAddReadOnlyDeployKey(t *testing.T) { - defer test.MockVariableValue(&setting.SSH.RootPath, t.TempDir())() +func TestAddDeployKey(t *testing.T) { unittest.PrepareTestEnv(t) - - ctx, _ := contexttest.MockContext(t, "user2/repo1/settings/keys") - - contexttest.LoadUser(t, ctx, 2) - contexttest.LoadRepo(t, ctx, 2) - - addKeyForm := forms.AddKeyForm{ - Title: "read-only", - Content: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment\n", - } - web.SetForm(ctx, &addKeyForm) - DeployKeysPost(ctx) - assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) - - unittest.AssertExistsAndLoadBean(t, &asymkey_model.DeployKey{ - Name: addKeyForm.Title, - Content: addKeyForm.Content, - Mode: perm.AccessModeRead, + t.Run("ReadOnly", func(t *testing.T) { + const testKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICV0MGX/W9IvLA4FXpIuUcdDcbj5KX4syHgsTy7soVgf\n" + ctx, _ := contexttest.MockContext(t, "POST /user2/repo1/settings/keys") + contexttest.MockRequestPostForm(ctx.Req, url.Values{"title": {"read-only"}, "content": {testKey}}) + contexttest.LoadRepo(t, ctx, 2) + DeployKeysPost(ctx) + assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus()) + unittest.AssertExistsAndLoadBean(t, &asymkey_model.DeployKey{Name: "read-only", Content: testKey, Mode: perm.AccessModeRead}) }) -} - -func TestAddReadWriteOnlyDeployKey(t *testing.T) { - defer test.MockVariableValue(&setting.SSH.RootPath, t.TempDir())() - - unittest.PrepareTestEnv(t) - - ctx, _ := contexttest.MockContext(t, "user2/repo1/settings/keys") - - contexttest.LoadUser(t, ctx, 2) - contexttest.LoadRepo(t, ctx, 2) - - addKeyForm := forms.AddKeyForm{ - Title: "read-write", - Content: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment\n", - IsWritable: true, - } - web.SetForm(ctx, &addKeyForm) - DeployKeysPost(ctx) - assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) - - unittest.AssertExistsAndLoadBean(t, &asymkey_model.DeployKey{ - Name: addKeyForm.Title, - Content: addKeyForm.Content, - Mode: perm.AccessModeWrite, + t.Run("ReadWrite", func(t *testing.T) { + const testKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEHjnNEfE88W1pvBLdV3otv28x760gdmPao3lVD5uAt9\n" + ctx, _ := contexttest.MockContext(t, "POST /user2/repo1/settings/keys") + contexttest.MockRequestPostForm(ctx.Req, url.Values{"title": {"read-write"}, "content": {testKey}, "is_writable": {"on"}}) + contexttest.LoadRepo(t, ctx, 2) + DeployKeysPost(ctx) + assert.Equal(t, http.StatusOK, ctx.Resp.WrittenStatus()) + unittest.AssertExistsAndLoadBean(t, &asymkey_model.DeployKey{Name: "read-write", Content: testKey, Mode: perm.AccessModeWrite}) }) } diff --git a/routers/web/web.go b/routers/web/web.go index 4e30bd7adcf..8e986d8dac3 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -1225,7 +1225,7 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) { m.Group("/keys", func() { m.Combo("").Get(repo_setting.DeployKeys). - Post(web.Bind(forms.AddKeyForm{}), repo_setting.DeployKeysPost) + Post(repo_setting.DeployKeysPost) m.Post("/delete", repo_setting.DeleteDeployKey) }) diff --git a/services/context/context.go b/services/context/context.go index 89de5fb630a..a240cb2b9e1 100644 --- a/services/context/context.go +++ b/services/context/context.go @@ -26,6 +26,8 @@ import ( "gitea.dev/modules/web" "gitea.dev/modules/web/middleware" web_types "gitea.dev/modules/web/types" + + "gitea.com/go-chi/binding" ) // Render represents a template render @@ -81,17 +83,22 @@ func GetWebContext(ctx context.Context) *Context { return webCtx } -// ValidateContext is a special context for form validation middleware. It may be different from other contexts. -type ValidateContext struct { - *Base -} - // GetValidateContext gets a context for middleware form validation -func GetValidateContext(req *http.Request) (ctx *ValidateContext) { +func GetValidateContext(req *http.Request) (ctx *middleware.ValidateContext) { if ctxAPI, ok := req.Context().Value(apiContextKey).(*APIContext); ok { - ctx = &ValidateContext{Base: ctxAPI.Base} + ctx = &middleware.ValidateContext{ + Data: ctxAPI.Data, + Locale: ctxAPI.Locale, + Req: ctxAPI.Req, + Resp: ctxAPI.Resp, + } } else if ctxWeb, ok := req.Context().Value(WebContextKey).(*Context); ok { - ctx = &ValidateContext{Base: ctxWeb.Base} + ctx = &middleware.ValidateContext{ + Data: ctxWeb.Data, + Locale: ctxWeb.Locale, + Req: ctxWeb.Req, + Resp: ctxWeb.Resp, + } } else { panic("invalid context, expect either APIContext or Context") } @@ -254,15 +261,24 @@ func (ctx *Context) JSONOK() { ctx.JSON(http.StatusOK, map[string]any{"ok": true}) // this is only a dummy response, frontend seldom uses it } -func (ctx *Context) JSONError(msg any) { +func buildJsonErrorMap(msg any) map[string]any { switch v := msg.(type) { case string: - ctx.JSON(http.StatusBadRequest, map[string]any{"errorMessage": v, "renderFormat": "text"}) + return map[string]any{"errorMessage": v, "renderFormat": "text"} case template.HTML: - ctx.JSON(http.StatusBadRequest, map[string]any{"errorMessage": v, "renderFormat": "html"}) - default: - panic(fmt.Sprintf("unsupported type: %T", msg)) + return map[string]any{"errorMessage": v, "renderFormat": "html"} } + panic(fmt.Sprintf("unsupported type: %T", msg)) +} + +func (ctx *Context) JSONError(msg any) { + ctx.JSON(http.StatusBadRequest, buildJsonErrorMap(msg)) +} + +func (ctx *Context) JSONErrorWithField(msg any, field string) { + m := buildJsonErrorMap(msg) + m["errorFields"] = []string{field} + ctx.JSON(http.StatusBadRequest, m) } func (ctx *Context) JSONErrorNotFound(optMsg ...string) { @@ -270,5 +286,24 @@ func (ctx *Context) JSONErrorNotFound(optMsg ...string) { if msg == "" { msg = ctx.Locale.TrString("error.not_found") } - ctx.JSON(http.StatusNotFound, map[string]any{"errorMessage": msg, "renderFormat": "text"}) + ctx.JSON(http.StatusNotFound, buildJsonErrorMap(msg)) +} + +func GetFetchActionForm[T interface { + *E + middleware.Form +}, E any](ctx *Context) *E { + if web.IsFormSet(ctx) { + panic("don't mix fetch-action form validation with template-based form validation") + } + middleware.SkipTmplFormValidationError(ctx) + form := T(new(E)) + errs := binding.Bind(ctx.Req, form) + errorMessage, fieldName, _ := middleware.BuildValidationErrorForUser(form, ctx.Locale, errs) + if errorMessage != "" { + ctx.Resp.Header().Set("Content-Type", "application/json") + ctx.JSONErrorWithField(errorMessage, fieldName) + return nil + } + return form } diff --git a/services/contexttest/context_tests.go b/services/contexttest/context_tests.go index 98cd78dfc3a..80739ca6c0e 100644 --- a/services/contexttest/context_tests.go +++ b/services/contexttest/context_tests.go @@ -104,6 +104,12 @@ func MockPrivateContext(t *testing.T, reqPath string) (*context.PrivateContext, return ctx, resp } +func MockRequestPostForm(req *http.Request, formData url.Values) { + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.PostForm = formData + maps.Copy(req.Form, formData) +} + // LoadRepo load a repo into a test context. func LoadRepo(t *testing.T, ctx gocontext.Context, repoID int64) { var doer *user_model.User diff --git a/services/forms/admin.go b/services/forms/admin.go index 993bd7a4f11..06c51c4146f 100644 --- a/services/forms/admin.go +++ b/services/forms/admin.go @@ -41,19 +41,19 @@ type AdminEditBadgeForm struct { // Validate validates form fields func (f *AdminCreateBadgeForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // Validate validates form fields func (f *AdminEditBadgeForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // Validate validates form fields func (f *AdminCreateUserForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // AdminEditUserForm form for admin to create user @@ -82,7 +82,7 @@ type AdminEditUserForm struct { // Validate validates form fields func (f *AdminEditUserForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // AdminDashboardForm form for admin dashboard operations @@ -94,5 +94,5 @@ type AdminDashboardForm struct { // Validate validates form fields func (f *AdminDashboardForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } diff --git a/services/forms/auth_form.go b/services/forms/auth_form.go index 8de8accb210..92f9060bcfd 100644 --- a/services/forms/auth_form.go +++ b/services/forms/auth_form.go @@ -100,5 +100,5 @@ type AuthenticationForm struct { // Validate validates fields func (f *AuthenticationForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } diff --git a/services/forms/org.go b/services/forms/org.go index be3f64d630e..11f931ad140 100644 --- a/services/forms/org.go +++ b/services/forms/org.go @@ -31,7 +31,7 @@ type CreateOrgForm struct { // Validate validates the fields func (f *CreateOrgForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // UpdateOrgSettingForm form for updating organization settings @@ -48,7 +48,7 @@ type UpdateOrgSettingForm struct { // Validate validates the fields func (f *UpdateOrgSettingForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } type RenameOrgForm struct { @@ -76,5 +76,5 @@ type CreateTeamForm struct { // Validate validates the fields func (f *CreateTeamForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } diff --git a/services/forms/package_form.go b/services/forms/package_form.go index 0a2a0df8c76..7bc946e50ef 100644 --- a/services/forms/package_form.go +++ b/services/forms/package_form.go @@ -26,5 +26,5 @@ type PackageCleanupRuleForm struct { func (f *PackageCleanupRuleForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } diff --git a/services/forms/repo_branch_form.go b/services/forms/repo_branch_form.go index 1b1098eb5a7..ce3586561be 100644 --- a/services/forms/repo_branch_form.go +++ b/services/forms/repo_branch_form.go @@ -22,7 +22,7 @@ type NewBranchForm struct { // Validate validates the fields func (f *NewBranchForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // RenameBranchForm form for rename a branch @@ -34,5 +34,5 @@ type RenameBranchForm struct { // Validate validates the fields func (f *RenameBranchForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index 72aec26d4c3..a331193842b 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -50,7 +50,7 @@ type CreateRepoForm struct { // Validate validates the fields func (f *CreateRepoForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // MigrateRepoForm form for migrating repository @@ -86,7 +86,7 @@ type MigrateRepoForm struct { // Validate validates the fields func (f *MigrateRepoForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // RepoSettingForm form for changing repository settings @@ -163,7 +163,7 @@ type RepoSettingForm struct { // Validate validates the fields func (f *RepoSettingForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // ProtectBranchForm form for changing protected branch settings @@ -205,7 +205,7 @@ type ProtectBranchForm struct { // Validate validates the fields func (f *ProtectBranchForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // WebhookForm form for changing web hook @@ -268,7 +268,7 @@ type NewWebhookForm struct { // Validate validates the fields func (f *NewWebhookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // NewGogshookForm form for creating gogs hook @@ -281,7 +281,7 @@ type NewGogshookForm struct { // Validate validates the fields func (f *NewGogshookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // NewSlackHookForm form for creating slack hook @@ -298,13 +298,9 @@ type NewSlackHookForm struct { func (f *NewSlackHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) if !webhook.IsValidSlackChannel(strings.TrimSpace(f.Channel)) { - errs = append(errs, binding.Error{ - FieldNames: []string{"Channel"}, - Classification: "", - Message: ctx.Locale.TrString("repo.settings.add_webhook.invalid_channel_name"), - }) + errs = middleware.AddValidationError(errs, "Channel", ctx.Locale.TrString("repo.settings.add_webhook.invalid_channel_name")) } - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // NewDiscordHookForm form for creating discord hook @@ -318,7 +314,7 @@ type NewDiscordHookForm struct { // Validate validates the fields func (f *NewDiscordHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // NewDingtalkHookForm form for creating dingtalk hook @@ -330,7 +326,7 @@ type NewDingtalkHookForm struct { // Validate validates the fields func (f *NewDingtalkHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // NewTelegramHookForm form for creating telegram hook @@ -344,7 +340,7 @@ type NewTelegramHookForm struct { // Validate validates the fields func (f *NewTelegramHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // NewMatrixHookForm form for creating Matrix hook @@ -358,7 +354,7 @@ type NewMatrixHookForm struct { // Validate validates the fields func (f *NewMatrixHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // NewMSTeamsHookForm form for creating MS Teams hook @@ -370,7 +366,7 @@ type NewMSTeamsHookForm struct { // Validate validates the fields func (f *NewMSTeamsHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // NewFeishuHookForm form for creating feishu hook @@ -382,7 +378,7 @@ type NewFeishuHookForm struct { // Validate validates the fields func (f *NewFeishuHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // NewWechatWorkHookForm form for creating wechatwork hook @@ -394,7 +390,7 @@ type NewWechatWorkHookForm struct { // Validate validates the fields func (f *NewWechatWorkHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // NewPackagistHookForm form for creating packagist hook @@ -408,7 +404,7 @@ type NewPackagistHookForm struct { // Validate validates the fields func (f *NewPackagistHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // CreateIssueForm form for creating issue @@ -426,7 +422,7 @@ type CreateIssueForm struct { // Validate validates the fields func (f *CreateIssueForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // CreateCommentForm form for creating comment @@ -439,7 +435,7 @@ type CreateCommentForm struct { // Validate validates the fields func (f *CreateCommentForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // ReactionForm form for adding and removing reaction @@ -450,7 +446,7 @@ type ReactionForm struct { // Validate validates the fields func (f *ReactionForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // IssueLockForm form for locking an issue @@ -459,9 +455,9 @@ type IssueLockForm struct { } // Validate validates the fields -func (i *IssueLockForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { +func (f *IssueLockForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, i, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // CreateProjectForm form for creating a project @@ -489,7 +485,7 @@ type CreateMilestoneForm struct { // Validate validates the fields func (f *CreateMilestoneForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // CreateLabelForm form for creating label @@ -506,7 +502,7 @@ type CreateLabelForm struct { // Validate validates the fields func (f *CreateLabelForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // InitializeLabelsForm form for initializing labels @@ -517,7 +513,7 @@ type InitializeLabelsForm struct { // Validate validates the fields func (f *InitializeLabelsForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // MergePullRequestForm form for merging Pull Request @@ -571,7 +567,7 @@ func (f *MergePullRequestForm) UnmarshalJSON(b []byte) error { // Validate validates the fields func (f *MergePullRequestForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // CodeCommentForm form for adding code comments for PRs @@ -590,7 +586,7 @@ type CodeCommentForm struct { // Validate validates the fields func (f *CodeCommentForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // SubmitReviewForm for submitting a finished code review @@ -604,7 +600,7 @@ type SubmitReviewForm struct { // Validate validates the fields func (f *SubmitReviewForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // ReviewType will return the corresponding ReviewType for type @@ -665,7 +661,7 @@ type NewReleaseForm struct { // Validate validates the fields func (f *NewReleaseForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // GenerateReleaseNotesForm retrieves release notes recommendations. @@ -678,7 +674,7 @@ type GenerateReleaseNotesForm struct { // Validate validates the fields func (f *GenerateReleaseNotesForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // EditReleaseForm form for changing release @@ -693,7 +689,7 @@ type EditReleaseForm struct { // Validate validates the fields func (f *EditReleaseForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // __ __.__ __ .__ @@ -714,7 +710,7 @@ type NewWikiForm struct { // FIXME: use code generation to generate this method. func (f *NewWikiForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // ___________.__ ___________ __ @@ -733,7 +729,7 @@ type AddTimeManuallyForm struct { // Validate validates the fields func (f *AddTimeManuallyForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // SaveTopicForm form for save topics for repository diff --git a/services/forms/repo_form_editor.go b/services/forms/repo_form_editor.go index 392567ad4ff..ff4687170e7 100644 --- a/services/forms/repo_form_editor.go +++ b/services/forms/repo_form_editor.go @@ -26,7 +26,7 @@ type CommitCommonForm struct { func (f *CommitCommonForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } type CommitCommonFormInterface interface { diff --git a/services/forms/repo_tag_form.go b/services/forms/repo_tag_form.go index e7ec51d550b..38a41d0c4c2 100644 --- a/services/forms/repo_tag_form.go +++ b/services/forms/repo_tag_form.go @@ -22,5 +22,5 @@ type ProtectTagForm struct { // Validate validates the fields func (f *ProtectTagForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } diff --git a/services/forms/runner.go b/services/forms/runner.go index 2ea9862afbd..573724f7a61 100644 --- a/services/forms/runner.go +++ b/services/forms/runner.go @@ -20,5 +20,5 @@ type EditRunnerForm struct { // Validate validates form fields func (f *EditRunnerForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } diff --git a/services/forms/user_form.go b/services/forms/user_form.go index 75275fb5c72..32dfda2ef3b 100644 --- a/services/forms/user_form.go +++ b/services/forms/user_form.go @@ -77,7 +77,7 @@ type InstallForm struct { // Validate validates the fields func (f *InstallForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // _____ ____ _________________ ___ @@ -98,7 +98,7 @@ type RegisterForm struct { // Validate validates the fields func (f *RegisterForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // IsEmailDomainAllowed validates that the email address @@ -120,7 +120,7 @@ type MustChangePasswordForm struct { // Validate validates the fields func (f *MustChangePasswordForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // SignInForm form for signing in with user/password @@ -134,7 +134,7 @@ type SignInForm struct { // Validate validates the fields func (f *SignInForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // AuthorizationForm form for authorizing oauth2 clients @@ -154,7 +154,7 @@ type AuthorizationForm struct { // Validate validates the fields func (f *AuthorizationForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // GrantApplicationForm form for authorizing oauth2 clients @@ -170,7 +170,7 @@ type GrantApplicationForm struct { // Validate validates the fields func (f *GrantApplicationForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // AccessTokenForm for issuing access tokens from authorization codes or refresh tokens @@ -189,7 +189,7 @@ type AccessTokenForm struct { // Validate validates the fields func (f *AccessTokenForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // IntrospectTokenForm for introspecting tokens @@ -200,7 +200,7 @@ type IntrospectTokenForm struct { // Validate validates the fields func (f *IntrospectTokenForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // __________________________________________.___ _______ ________ _________ @@ -225,7 +225,7 @@ type UpdateProfileForm struct { // Validate validates the fields func (f *UpdateProfileForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // UpdateLanguageForm form for updating profile @@ -236,7 +236,7 @@ type UpdateLanguageForm struct { // Validate validates the fields func (f *UpdateLanguageForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // Avatar types @@ -256,7 +256,7 @@ type AvatarForm struct { // Validate validates the fields func (f *AvatarForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // AddEmailForm form for adding new email @@ -267,7 +267,7 @@ type AddEmailForm struct { // Validate validates the fields func (f *AddEmailForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // UpdateThemeForm form for updating a users' theme @@ -278,7 +278,7 @@ type UpdateThemeForm struct { // Validate validates the field func (f *UpdateThemeForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // ChangePasswordForm form for changing password @@ -291,7 +291,7 @@ type ChangePasswordForm struct { // Validate validates the fields func (f *ChangePasswordForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // AddOpenIDForm is for changing openid uri @@ -302,7 +302,7 @@ type AddOpenIDForm struct { // Validate validates the fields func (f *AddOpenIDForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // AddKeyForm form for adding SSH/GPG key @@ -319,7 +319,7 @@ type AddKeyForm struct { // Validate validates the fields func (f *AddKeyForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // AddSecretForm for adding secrets @@ -332,7 +332,7 @@ type AddSecretForm struct { // Validate validates the fields func (f *AddSecretForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } type EditVariableForm struct { @@ -343,7 +343,7 @@ type EditVariableForm struct { func (f *EditVariableForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // NewAccessTokenForm form for creating access token @@ -354,7 +354,7 @@ type NewAccessTokenForm struct { // Validate validates the fields func (f *NewAccessTokenForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // EditOAuth2ApplicationForm form for editing oauth2 applications @@ -381,9 +381,9 @@ func (f *EditOAuth2ApplicationForm) Validate(req *http.Request, errs binding.Err ctx := context.GetValidateContext(req) invalidURI := DetectInvalidOAuth2ApplicationRedirectURI(util.SplitTrimSpace(f.RedirectURIs, "\n")) if invalidURI != "" { - errs = middleware.ReportValidationError(errs, ctx.Data, "RedirectURIs", binding.ERR_URL, ctx.Locale.TrString("form.url_error", invalidURI)) + errs = middleware.AddValidationError(errs, "RedirectURIs", "RedirectURIs: "+ctx.Locale.TrString("form.url_error", `"`+invalidURI+`"`)) } - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // TwoFactorAuthForm for logging in with 2FA token. @@ -394,7 +394,7 @@ type TwoFactorAuthForm struct { // Validate validates the fields func (f *TwoFactorAuthForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // TwoFactorScratchAuthForm for logging in with 2FA scratch token. @@ -405,7 +405,7 @@ type TwoFactorScratchAuthForm struct { // Validate validates the fields func (f *TwoFactorScratchAuthForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // WebauthnRegistrationForm for reserving an WebAuthn name @@ -416,7 +416,7 @@ type WebauthnRegistrationForm struct { // Validate validates the fields func (f *WebauthnRegistrationForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // PackageSettingForm form for package settings @@ -428,7 +428,7 @@ type PackageSettingForm struct { // Validate validates the fields func (f *PackageSettingForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } type BlockUserForm struct { @@ -439,5 +439,5 @@ type BlockUserForm struct { func (f *BlockUserForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } diff --git a/services/forms/user_form_auth_openid.go b/services/forms/user_form_auth_openid.go index 8da80e4e1eb..9fee359c2f2 100644 --- a/services/forms/user_form_auth_openid.go +++ b/services/forms/user_form_auth_openid.go @@ -21,7 +21,7 @@ type SignInOpenIDForm struct { // Validate validates the fields func (f *SignInOpenIDForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // SignUpOpenIDForm form for signin up with OpenID @@ -33,7 +33,7 @@ type SignUpOpenIDForm struct { // Validate validates the fields func (f *SignUpOpenIDForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } // ConnectOpenIDForm form for connecting an existing account to an OpenID URI @@ -45,5 +45,5 @@ type ConnectOpenIDForm struct { // Validate validates the fields func (f *ConnectOpenIDForm) Validate(req *http.Request, errs binding.Errors) binding.Errors { ctx := context.GetValidateContext(req) - return middleware.Validate(errs, ctx.Data, f, ctx.Locale) + return middleware.Validate(ctx, errs, f) } diff --git a/templates/repo/settings/deploy_keys.tmpl b/templates/repo/settings/deploy_keys.tmpl index 89a9df36678..588968f877c 100644 --- a/templates/repo/settings/deploy_keys.tmpl +++ b/templates/repo/settings/deploy_keys.tmpl @@ -11,21 +11,21 @@
-
-
+
+
{{ctx.Locale.Tr "repo.settings.deploy_key_desc"}}
-
+
-
+
-
+