mirror of
https://github.com/go-gitea/gitea.git
synced 2026-08-11 04:32:27 +00:00
refactor: form binding validation (#38832)
Make "form-fetch-action" highlight the invalid field as "error"
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -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)
|
||||
})
|
||||
|
||||
|
||||
+49
-14
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
+32
-36
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
+26
-26
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -11,21 +11,21 @@
|
||||
</div>
|
||||
</h4>
|
||||
<div class="ui attached segment">
|
||||
<div class="{{if not .HasError}}tw-hidden{{end}} tw-mb-4" id="add-deploy-key-panel">
|
||||
<form class="ui form" action="{{.Link}}" method="post">
|
||||
<div class="tw-hidden tw-mb-4" id="add-deploy-key-panel">
|
||||
<form class="ui form form-fetch-action" action="{{.Link}}" method="post">
|
||||
<div class="field">
|
||||
{{ctx.Locale.Tr "repo.settings.deploy_key_desc"}}
|
||||
</div>
|
||||
<div class="field {{if .Err_Title}}error{{end}}">
|
||||
<div class="field">
|
||||
<label for="ssh-key-title">{{ctx.Locale.Tr "repo.settings.title"}}</label>
|
||||
<input id="ssh-key-title" name="title" value="{{.title}}" autofocus required>
|
||||
</div>
|
||||
<div class="field {{if .Err_Content}}error{{end}}">
|
||||
<div class="field">
|
||||
<label for="ssh-key-content">{{ctx.Locale.Tr "repo.settings.deploy_key_content"}}</label>
|
||||
<textarea id="ssh-key-content" name="content" placeholder="{{ctx.Locale.Tr "settings.key_content_ssh_placeholder"}}" required>{{.content}}</textarea>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui checkbox {{if .Err_IsWritable}}error{{end}}">
|
||||
<div class="ui checkbox">
|
||||
<input id="ssh-key-is-writable" name="is_writable" type="checkbox" value="1">
|
||||
<label for="ssh-key-is-writable">
|
||||
{{ctx.Locale.Tr "repo.settings.is_writable"}}
|
||||
|
||||
@@ -53,7 +53,7 @@ func testCreateBranches(t *testing.T, giteaURL *url.URL) {
|
||||
OldRefSubURL: "branch/master",
|
||||
NewBranch: "",
|
||||
ExpectedStatus: http.StatusSeeOther,
|
||||
FlashMessage: translation.NewLocale("en-US").TrString("form.NewBranchName") + translation.NewLocale("en-US").TrString("form.require_error"),
|
||||
FlashMessage: translation.NewLocale("en-US").TrString("form.require_error", translation.NewLocale("en-US").TrString("form.NewBranchName")),
|
||||
},
|
||||
{
|
||||
OldRefSubURL: "branch/master",
|
||||
@@ -65,7 +65,7 @@ func testCreateBranches(t *testing.T, giteaURL *url.URL) {
|
||||
OldRefSubURL: "branch/master",
|
||||
NewBranch: strings.Repeat("b", 101),
|
||||
ExpectedStatus: http.StatusSeeOther,
|
||||
FlashMessage: translation.NewLocale("en-US").TrString("form.NewBranchName") + translation.NewLocale("en-US").TrString("form.max_size_error", "100"),
|
||||
FlashMessage: translation.NewLocale("en-US").TrString("form.max_size_error", translation.NewLocale("en-US").TrString("form.NewBranchName"), "100"),
|
||||
},
|
||||
{
|
||||
OldRefSubURL: "branch/master",
|
||||
|
||||
@@ -184,7 +184,7 @@ func testRenameInvalidUsername(t *testing.T) {
|
||||
htmlDoc := NewHTMLParser(t, resp.Body)
|
||||
assert.Contains(t,
|
||||
htmlDoc.doc.Find(".ui.negative.message").Text(),
|
||||
translation.NewLocale("en-US").TrString("form.username_error"),
|
||||
translation.NewLocale("en-US").TrString("form.username_error", "Name"),
|
||||
)
|
||||
|
||||
unittest.AssertNotExistsBean(t, &user_model.User{Name: invalidUsername})
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import {execPseudoSelectorCommands, handleFetchActionSuccessJson} from './common-fetch-action.ts';
|
||||
import {execPseudoSelectorCommands, handleFetchActionErrorFields, handleFetchActionSuccessJson} from './common-fetch-action.ts';
|
||||
import {createElementFromHTML} from '../utils/dom.ts';
|
||||
import {normalizeTestHtml} from '../utils/testhelper.ts';
|
||||
|
||||
test('execPseudoSelectorCommands', () => {
|
||||
window.document.body.innerHTML = `
|
||||
@@ -57,3 +59,15 @@ test('handleFetchActionSuccessJson', async () => {
|
||||
expect(spyReload).toHaveBeenCalledTimes(1);
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
test('handleFetchActionErrorFields', () => {
|
||||
const elForm = createElementFromHTML<HTMLElement>(`<form>
|
||||
<div class="error field"></div>
|
||||
<div class="field"><input name="Foo_Bar[]"></div>
|
||||
</form>`);
|
||||
handleFetchActionErrorFields(elForm, ['foo-BAR', 'other']);
|
||||
expect(normalizeTestHtml(elForm.outerHTML)).toEqual(normalizeTestHtml(`<form>
|
||||
<div class="field"></div>
|
||||
<div class="field error"><input name="Foo_Bar[]"></div>
|
||||
</form>`));
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {GET, request} from '../modules/fetch.ts';
|
||||
import {hideToastsAll, showErrorToast} from '../modules/toast.ts';
|
||||
import {activePageTimerRefresh, addDelegatedEventListener, createElementFromHTML} from '../utils/dom.ts';
|
||||
import {activePageTimerRefresh, addDelegatedEventListener, createElementFromHTML, queryElems} from '../utils/dom.ts';
|
||||
import {errorMessage, errorName} from '../modules/errors.ts';
|
||||
import {confirmModal, createConfirmModal} from './comp/ConfirmModal.ts';
|
||||
import {ignoreAreYouSure} from '../vendor/jquery.are-you-sure.ts';
|
||||
@@ -92,7 +92,34 @@ async function handleFetchActionSuccess(el: HTMLElement, opt: FetchActionOpts, r
|
||||
}
|
||||
}
|
||||
|
||||
async function handleFetchActionError(resp: Response) {
|
||||
function resetFormErrorFields(elForm: HTMLFormElement) {
|
||||
queryElems(elForm, '.field.error', (el) => el.classList.remove('error'));
|
||||
}
|
||||
|
||||
export function handleFetchActionErrorFields(el: HTMLElement, errorFields: string[]) {
|
||||
// The "error field" only works in a form.
|
||||
// And non-form requests do not need such "error field" because the requests are just from a button or a link in such cases.
|
||||
if (el.nodeName !== 'FORM') return;
|
||||
const elForm = el as HTMLFormElement;
|
||||
resetFormErrorFields(elForm);
|
||||
const fieldNameElemMap : Record<string, HTMLElement> = {};
|
||||
const normalizeFieldName = (name: string) => name.replace(/[^A-Za-z0-9]/g, '').toLowerCase();
|
||||
queryElems(elForm, '[name]', (el) => {
|
||||
const name = el.getAttribute('name');
|
||||
if (!name) return;
|
||||
fieldNameElemMap[normalizeFieldName(name)] = el;
|
||||
});
|
||||
for (const errorField of errorFields) {
|
||||
const name = normalizeFieldName(errorField);
|
||||
const elInput = fieldNameElemMap[name];
|
||||
if (!elInput) continue;
|
||||
const elField = elInput.closest('.field');
|
||||
if (!elField) continue;
|
||||
elField.classList.add('error');
|
||||
}
|
||||
}
|
||||
|
||||
async function handleFetchActionError(el: HTMLElement, resp: Response) {
|
||||
const isRespJson = resp.headers.get('content-type')?.includes('application/json');
|
||||
const respText = await resp.text();
|
||||
const respJson = isRespJson ? JSON.parse(respText) : null;
|
||||
@@ -100,6 +127,9 @@ async function handleFetchActionError(resp: Response) {
|
||||
// the code was quite messy, sometimes the backend uses "err", sometimes it uses "error", and even "user_error"
|
||||
// but at the moment, as a new approach, we only use "errorMessage" here, backend can use JSONError() to respond.
|
||||
showErrorToast(respJson.errorMessage, {useHtmlBody: respJson.renderFormat === 'html'});
|
||||
if (respJson?.errorFields?.length) {
|
||||
handleFetchActionErrorFields(el, respJson?.errorFields);
|
||||
}
|
||||
} else {
|
||||
showErrorToast(`Error ${resp.status} ${resp.statusText}. Response: ${respText.substring(0, 200)}`);
|
||||
}
|
||||
@@ -135,7 +165,7 @@ export async function performFetchActionRequest(el: HTMLElement, opt: FetchActio
|
||||
headers.set('X-Gitea-Fetch-Action', '1');
|
||||
const resp = await request(url, {method: opt.method, data: opt.data, headers});
|
||||
if (resp.ok) return resp;
|
||||
await handleFetchActionError(resp);
|
||||
await handleFetchActionError(el, resp);
|
||||
} catch (err) {
|
||||
if (errorName(err) !== 'AbortError') {
|
||||
console.error(`Fetch action request error:`, err);
|
||||
@@ -194,9 +224,10 @@ function prepareFormFetchActionOpts(formEl: HTMLFormElement, opts: SubmitFormFet
|
||||
};
|
||||
}
|
||||
|
||||
export async function submitFormFetchAction(formEl: HTMLFormElement, opts: SubmitFormFetchActionOpts = {}) {
|
||||
export async function submitFormFetchAction(elForm: HTMLFormElement, opts: SubmitFormFetchActionOpts = {}) {
|
||||
hideToastsAll();
|
||||
await performFetchAction(formEl, prepareFormFetchActionOpts(formEl, opts));
|
||||
resetFormErrorFields(elForm);
|
||||
await performFetchAction(elForm, prepareFormFetchActionOpts(elForm, opts));
|
||||
}
|
||||
|
||||
async function confirmFetchAction(el: HTMLElement) {
|
||||
|
||||
Reference in New Issue
Block a user