summaryrefslogtreecommitdiffstats
path: root/services/forms
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--services/forms/admin.go74
-rw-r--r--services/forms/auth_form.go92
-rw-r--r--services/forms/org.go76
-rw-r--r--services/forms/package_form.go30
-rw-r--r--services/forms/repo_branch_form.go38
-rw-r--r--services/forms/repo_form.go751
-rw-r--r--services/forms/repo_form_test.go64
-rw-r--r--services/forms/repo_tag_form.go26
-rw-r--r--services/forms/runner.go24
-rw-r--r--services/forms/user_form.go455
-rw-r--r--services/forms/user_form_auth_openid.go49
-rw-r--r--services/forms/user_form_hidden_comments.go104
-rw-r--r--services/forms/user_form_test.go131
13 files changed, 1914 insertions, 0 deletions
diff --git a/services/forms/admin.go b/services/forms/admin.go
new file mode 100644
index 0000000..7d46904
--- /dev/null
+++ b/services/forms/admin.go
@@ -0,0 +1,74 @@
+// Copyright 2014 The Gogs Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package forms
+
+import (
+ "net/http"
+
+ "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/web/middleware"
+ "code.gitea.io/gitea/services/context"
+
+ "gitea.com/go-chi/binding"
+)
+
+// AdminCreateUserForm form for admin to create user
+type AdminCreateUserForm struct {
+ LoginType string `binding:"Required"`
+ LoginName string
+ UserName string `binding:"Required;Username;MaxSize(40)"`
+ Email string `binding:"Required;Email;MaxSize(254)"`
+ Password string `binding:"MaxSize(255)"`
+ SendNotify bool
+ MustChangePassword bool
+ Visibility structs.VisibleType
+}
+
+// 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)
+}
+
+// AdminEditUserForm form for admin to create user
+type AdminEditUserForm struct {
+ LoginType string `binding:"Required"`
+ UserName string `binding:"Username;MaxSize(40)"`
+ LoginName string
+ FullName string `binding:"MaxSize(100)"`
+ Email string `binding:"Required;Email;MaxSize(254)"`
+ Password string `binding:"MaxSize(255)"`
+ Website string `binding:"ValidUrl;MaxSize(255)"`
+ Location string `binding:"MaxSize(50)"`
+ Language string `binding:"MaxSize(5)"`
+ Pronouns string `binding:"MaxSize(50)"`
+ MaxRepoCreation int
+ Active bool
+ Admin bool
+ Restricted bool
+ AllowGitHook bool
+ AllowImportLocal bool
+ AllowCreateOrganization bool
+ ProhibitLogin bool
+ Reset2FA bool `form:"reset_2fa"`
+ Visibility structs.VisibleType
+}
+
+// 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)
+}
+
+// AdminDashboardForm form for admin dashboard operations
+type AdminDashboardForm struct {
+ Op string `binding:"required"`
+ From string
+}
+
+// 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)
+}
diff --git a/services/forms/auth_form.go b/services/forms/auth_form.go
new file mode 100644
index 0000000..a3eca94
--- /dev/null
+++ b/services/forms/auth_form.go
@@ -0,0 +1,92 @@
+// Copyright 2014 The Gogs Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package forms
+
+import (
+ "net/http"
+
+ "code.gitea.io/gitea/modules/web/middleware"
+ "code.gitea.io/gitea/services/context"
+
+ "gitea.com/go-chi/binding"
+)
+
+// AuthenticationForm form for authentication
+type AuthenticationForm struct {
+ ID int64
+ Type int `binding:"Range(2,7)"`
+ Name string `binding:"Required;MaxSize(30)"`
+ Host string
+ Port int
+ BindDN string
+ BindPassword string
+ UserBase string
+ UserDN string
+ AttributeUsername string
+ AttributeName string
+ AttributeSurname string
+ DefaultDomainName string
+ AttributeMail string
+ AttributeSSHPublicKey string
+ AttributeAvatar string
+ AttributesInBind bool
+ UsePagedSearch bool
+ SearchPageSize int
+ Filter string
+ AdminFilter string
+ GroupsEnabled bool
+ GroupDN string
+ GroupFilter string
+ GroupMemberUID string
+ UserUID string
+ RestrictedFilter string
+ AllowDeactivateAll bool
+ IsActive bool
+ IsSyncEnabled bool
+ SMTPAuth string
+ SMTPHost string
+ SMTPPort int
+ AllowedDomains string
+ SecurityProtocol int `binding:"Range(0,2)"`
+ TLS bool
+ SkipVerify bool
+ HeloHostname string
+ DisableHelo bool
+ ForceSMTPS bool
+ PAMServiceName string
+ PAMEmailDomain string
+ Oauth2Provider string
+ Oauth2Key string
+ Oauth2Secret string
+ OpenIDConnectAutoDiscoveryURL string
+ Oauth2UseCustomURL bool
+ Oauth2TokenURL string
+ Oauth2AuthURL string
+ Oauth2ProfileURL string
+ Oauth2EmailURL string
+ Oauth2IconURL string
+ Oauth2Tenant string
+ Oauth2Scopes string
+ Oauth2RequiredClaimName string
+ Oauth2RequiredClaimValue string
+ Oauth2GroupClaimName string
+ Oauth2AdminGroup string
+ Oauth2RestrictedGroup string
+ Oauth2GroupTeamMap string `binding:"ValidGroupTeamMap"`
+ Oauth2GroupTeamMapRemoval bool
+ SkipLocalTwoFA bool
+ SSPIAutoCreateUsers bool
+ SSPIAutoActivateUsers bool
+ SSPIStripDomainNames bool
+ SSPISeparatorReplacement string `binding:"AlphaDashDot;MaxSize(5)"`
+ SSPIDefaultLanguage string
+ GroupTeamMap string `binding:"ValidGroupTeamMap"`
+ GroupTeamMapRemoval bool
+}
+
+// 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)
+}
diff --git a/services/forms/org.go b/services/forms/org.go
new file mode 100644
index 0000000..db182f7
--- /dev/null
+++ b/services/forms/org.go
@@ -0,0 +1,76 @@
+// Copyright 2014 The Gogs Authors. All rights reserved.
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package forms
+
+import (
+ "net/http"
+
+ "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/web/middleware"
+ "code.gitea.io/gitea/services/context"
+
+ "gitea.com/go-chi/binding"
+)
+
+// ________ .__ __ .__
+// \_____ \_______ _________ ____ |__|____________ _/ |_|__| ____ ____
+// / | \_ __ \/ ___\__ \ / \| \___ /\__ \\ __\ |/ _ \ / \
+// / | \ | \/ /_/ > __ \| | \ |/ / / __ \| | | ( <_> ) | \
+// \_______ /__| \___ (____ /___| /__/_____ \(____ /__| |__|\____/|___| /
+// \/ /_____/ \/ \/ \/ \/ \/
+
+// CreateOrgForm form for creating organization
+type CreateOrgForm struct {
+ OrgName string `binding:"Required;Username;MaxSize(40)" locale:"org.org_name_holder"`
+ Visibility structs.VisibleType
+ RepoAdminChangeTeamAccess bool
+}
+
+// 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)
+}
+
+// UpdateOrgSettingForm form for updating organization settings
+type UpdateOrgSettingForm struct {
+ Name string `binding:"Required;Username;MaxSize(40)" locale:"org.org_name_holder"`
+ FullName string `binding:"MaxSize(100)"`
+ Email string `binding:"MaxSize(255)"`
+ Description string `binding:"MaxSize(255)"`
+ Website string `binding:"ValidUrl;MaxSize(255)"`
+ Location string `binding:"MaxSize(50)"`
+ Visibility structs.VisibleType
+ MaxRepoCreation int
+ RepoAdminChangeTeamAccess bool
+}
+
+// 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)
+}
+
+// ___________
+// \__ ___/___ _____ _____
+// | |_/ __ \\__ \ / \
+// | |\ ___/ / __ \| Y Y \
+// |____| \___ >____ /__|_| /
+// \/ \/ \/
+
+// CreateTeamForm form for creating team
+type CreateTeamForm struct {
+ TeamName string `binding:"Required;AlphaDashDot;MaxSize(255)"`
+ Description string `binding:"MaxSize(255)"`
+ Permission string
+ RepoAccess string
+ CanCreateOrgRepo bool
+}
+
+// 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)
+}
diff --git a/services/forms/package_form.go b/services/forms/package_form.go
new file mode 100644
index 0000000..9b6f907
--- /dev/null
+++ b/services/forms/package_form.go
@@ -0,0 +1,30 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package forms
+
+import (
+ "net/http"
+
+ "code.gitea.io/gitea/modules/web/middleware"
+ "code.gitea.io/gitea/services/context"
+
+ "gitea.com/go-chi/binding"
+)
+
+type PackageCleanupRuleForm struct {
+ ID int64
+ Enabled bool
+ Type string `binding:"Required;In(alpine,arch,cargo,chef,composer,conan,conda,container,cran,debian,generic,go,helm,maven,npm,nuget,pub,pypi,rpm,rubygems,swift,vagrant)"`
+ KeepCount int `binding:"In(0,1,5,10,25,50,100)"`
+ KeepPattern string `binding:"RegexPattern"`
+ RemoveDays int `binding:"In(0,7,14,30,60,90,180)"`
+ RemovePattern string `binding:"RegexPattern"`
+ MatchFullName bool
+ Action string `binding:"Required;In(save,remove)"`
+}
+
+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)
+}
diff --git a/services/forms/repo_branch_form.go b/services/forms/repo_branch_form.go
new file mode 100644
index 0000000..42e6c85
--- /dev/null
+++ b/services/forms/repo_branch_form.go
@@ -0,0 +1,38 @@
+// Copyright 2017 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package forms
+
+import (
+ "net/http"
+
+ "code.gitea.io/gitea/modules/web/middleware"
+ "code.gitea.io/gitea/services/context"
+
+ "gitea.com/go-chi/binding"
+)
+
+// NewBranchForm form for creating a new branch
+type NewBranchForm struct {
+ NewBranchName string `binding:"Required;MaxSize(100);GitRefName"`
+ CurrentPath string
+ CreateTag bool
+}
+
+// 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)
+}
+
+// RenameBranchForm form for rename a branch
+type RenameBranchForm struct {
+ From string `binding:"Required;MaxSize(100);GitRefName"`
+ To string `binding:"Required;MaxSize(100);GitRefName"`
+}
+
+// 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)
+}
diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go
new file mode 100644
index 0000000..c3d9c3e
--- /dev/null
+++ b/services/forms/repo_form.go
@@ -0,0 +1,751 @@
+// Copyright 2024 The Forgejo Authors. All rights reserved.
+// Copyright 2014 The Gogs Authors. All rights reserved.
+// Copyright 2017 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package forms
+
+import (
+ "fmt"
+ "net/http"
+ "net/url"
+ "regexp"
+ "strings"
+
+ "code.gitea.io/gitea/models"
+ issues_model "code.gitea.io/gitea/models/issues"
+ project_model "code.gitea.io/gitea/models/project"
+ webhook_model "code.gitea.io/gitea/models/webhook"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/web/middleware"
+ "code.gitea.io/gitea/services/context"
+
+ "gitea.com/go-chi/binding"
+)
+
+// CreateRepoForm form for creating repository
+type CreateRepoForm struct {
+ UID int64 `binding:"Required"`
+ RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"`
+ Private bool
+ Description string `binding:"MaxSize(2048)"`
+ DefaultBranch string `binding:"GitRefName;MaxSize(100)"`
+ AutoInit bool
+ Gitignores string
+ IssueLabels string
+ License string
+ Readme string
+ Template bool
+
+ RepoTemplate int64
+ GitContent bool
+ Topics bool
+ GitHooks bool
+ Webhooks bool
+ Avatar bool
+ Labels bool
+ ProtectedBranch bool
+
+ ForkSingleBranch string
+ ObjectFormatName string
+}
+
+// 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)
+}
+
+// MigrateRepoForm form for migrating repository
+// this is used to interact with web ui
+type MigrateRepoForm struct {
+ // required: true
+ CloneAddr string `json:"clone_addr" binding:"Required"`
+ Service structs.GitServiceType `json:"service"`
+ AuthUsername string `json:"auth_username"`
+ AuthPassword string `json:"auth_password"`
+ AuthToken string `json:"auth_token"`
+ // required: true
+ UID int64 `json:"uid" binding:"Required"`
+ // required: true
+ RepoName string `json:"repo_name" binding:"Required;AlphaDashDot;MaxSize(100)"`
+ Mirror bool `json:"mirror"`
+ LFS bool `json:"lfs"`
+ LFSEndpoint string `json:"lfs_endpoint"`
+ Private bool `json:"private"`
+ Description string `json:"description" binding:"MaxSize(2048)"`
+ Wiki bool `json:"wiki"`
+ Milestones bool `json:"milestones"`
+ Labels bool `json:"labels"`
+ Issues bool `json:"issues"`
+ PullRequests bool `json:"pull_requests"`
+ Releases bool `json:"releases"`
+ MirrorInterval string `json:"mirror_interval"`
+}
+
+// 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)
+}
+
+// scpRegex matches the SCP-like addresses used by Git to access repositories over SSH.
+var scpRegex = regexp.MustCompile(`^([a-zA-Z0-9_]+)@([a-zA-Z0-9._-]+):(.*)$`)
+
+// ParseRemoteAddr checks if given remote address is valid,
+// and returns composed URL with needed username and password.
+func ParseRemoteAddr(remoteAddr, authUsername, authPassword string) (string, error) {
+ remoteAddr = strings.TrimSpace(remoteAddr)
+ // Remote address can be HTTP/HTTPS/Git URL or local path.
+ if strings.HasPrefix(remoteAddr, "http://") ||
+ strings.HasPrefix(remoteAddr, "https://") ||
+ strings.HasPrefix(remoteAddr, "git://") {
+ u, err := url.Parse(remoteAddr)
+ if err != nil {
+ return "", &models.ErrInvalidCloneAddr{IsURLError: true, Host: remoteAddr}
+ }
+ if len(authUsername)+len(authPassword) > 0 {
+ u.User = url.UserPassword(authUsername, authPassword)
+ }
+ return u.String(), nil
+ }
+
+ // Detect SCP-like remote addresses and return host.
+ if m := scpRegex.FindStringSubmatch(remoteAddr); m != nil {
+ // Match SCP-like syntax and convert it to a URL.
+ // Eg, "git@forgejo.org:user/repo" becomes
+ // "ssh://git@forgejo.org/user/repo".
+ return fmt.Sprintf("ssh://%s@%s/%s", url.User(m[1]), m[2], m[3]), nil
+ }
+
+ return remoteAddr, nil
+}
+
+// RepoSettingForm form for changing repository settings
+type RepoSettingForm struct {
+ RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"`
+ Description string `binding:"MaxSize(2048)"`
+ Website string `binding:"ValidUrl;MaxSize(1024)"`
+ FollowingRepos string
+ Interval string
+ MirrorAddress string
+ MirrorUsername string
+ MirrorPassword string
+ LFS bool `form:"mirror_lfs"`
+ LFSEndpoint string `form:"mirror_lfs_endpoint"`
+ PushMirrorID string
+ PushMirrorAddress string
+ PushMirrorUsername string
+ PushMirrorPassword string
+ PushMirrorSyncOnCommit bool
+ PushMirrorInterval string
+ PushMirrorUseSSH bool
+ Private bool
+ Template bool
+ EnablePrune bool
+
+ // Advanced settings
+ IsArchived bool
+
+ // Signing Settings
+ TrustModel string
+
+ // Admin settings
+ EnableHealthCheck bool
+ RequestReindexType string
+}
+
+// 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)
+}
+
+// RepoUnitSettingForm form for changing repository unit settings
+type RepoUnitSettingForm struct {
+ EnableCode bool
+ EnableWiki bool
+ GloballyWriteableWiki bool
+ EnableExternalWiki bool
+ ExternalWikiURL string
+ EnableIssues bool
+ EnableExternalTracker bool
+ ExternalTrackerURL string
+ TrackerURLFormat string
+ TrackerIssueStyle string
+ ExternalTrackerRegexpPattern string
+ EnableCloseIssuesViaCommitInAnyBranch bool
+ EnableProjects bool
+ EnableReleases bool
+ EnablePackages bool
+ EnablePulls bool
+ EnableActions bool
+ PullsIgnoreWhitespace bool
+ PullsAllowMerge bool
+ PullsAllowRebase bool
+ PullsAllowRebaseMerge bool
+ PullsAllowSquash bool
+ PullsAllowFastForwardOnly bool
+ PullsAllowManualMerge bool
+ PullsDefaultMergeStyle string
+ EnableAutodetectManualMerge bool
+ PullsAllowRebaseUpdate bool
+ DefaultDeleteBranchAfterMerge bool
+ DefaultAllowMaintainerEdit bool
+ EnableTimetracker bool
+ AllowOnlyContributorsToTrackTime bool
+ EnableIssueDependencies bool
+}
+
+// Validate validates the fields
+func (f *RepoUnitSettingForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetValidateContext(req)
+ return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// __________ .__
+// \______ \____________ ____ ____ | |__
+// | | _/\_ __ \__ \ / \_/ ___\| | \
+// | | \ | | \// __ \| | \ \___| Y \
+// |______ / |__| (____ /___| /\___ >___| /
+// \/ \/ \/ \/ \/
+
+// ProtectBranchForm form for changing protected branch settings
+type ProtectBranchForm struct {
+ RuleName string `binding:"Required"`
+ RuleID int64
+ EnablePush string
+ WhitelistUsers string
+ WhitelistTeams string
+ WhitelistDeployKeys bool
+ EnableMergeWhitelist bool
+ MergeWhitelistUsers string
+ MergeWhitelistTeams string
+ EnableStatusCheck bool
+ StatusCheckContexts string
+ RequiredApprovals int64
+ EnableApprovalsWhitelist bool
+ ApprovalsWhitelistUsers string
+ ApprovalsWhitelistTeams string
+ BlockOnRejectedReviews bool
+ BlockOnOfficialReviewRequests bool
+ BlockOnOutdatedBranch bool
+ DismissStaleApprovals bool
+ IgnoreStaleApprovals bool
+ RequireSignedCommits bool
+ ProtectedFilePatterns string
+ UnprotectedFilePatterns string
+ ApplyToAdmins bool
+}
+
+// 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)
+}
+
+// __ __ ___. .__ __
+// / \ / \ ____\_ |__ | |__ ____ ____ | | __
+// \ \/\/ // __ \| __ \| | \ / _ \ / _ \| |/ /
+// \ /\ ___/| \_\ \ Y ( <_> | <_> ) <
+// \__/\ / \___ >___ /___| /\____/ \____/|__|_ \
+// \/ \/ \/ \/ \/
+
+// WebhookCoreForm form for changing web hook (common to all webhook types)
+type WebhookCoreForm struct {
+ Events string
+ Create bool
+ Delete bool
+ Fork bool
+ Issues bool
+ IssueAssign bool
+ IssueLabel bool
+ IssueMilestone bool
+ IssueComment bool
+ Release bool
+ Push bool
+ PullRequest bool
+ PullRequestAssign bool
+ PullRequestLabel bool
+ PullRequestMilestone bool
+ PullRequestComment bool
+ PullRequestReview bool
+ PullRequestSync bool
+ PullRequestReviewRequest bool
+ Wiki bool
+ Repository bool
+ Package bool
+ Active bool
+ BranchFilter string `binding:"GlobPattern"`
+ AuthorizationHeader string
+}
+
+// PushOnly if the hook will be triggered when push
+func (f WebhookCoreForm) PushOnly() bool {
+ return f.Events == "push_only"
+}
+
+// SendEverything if the hook will be triggered any event
+func (f WebhookCoreForm) SendEverything() bool {
+ return f.Events == "send_everything"
+}
+
+// ChooseEvents if the hook will be triggered choose events
+func (f WebhookCoreForm) ChooseEvents() bool {
+ return f.Events == "choose_events"
+}
+
+// WebhookForm form for changing web hook (specific handling depending on the webhook type)
+type WebhookForm struct {
+ WebhookCoreForm
+ URL string
+ ContentType webhook_model.HookContentType
+ Secret string
+ HTTPMethod string
+ Metadata any
+}
+
+// .___
+// | | ______ ________ __ ____
+// | |/ ___// ___/ | \_/ __ \
+// | |\___ \ \___ \| | /\ ___/
+// |___/____ >____ >____/ \___ >
+// \/ \/ \/
+
+// CreateIssueForm form for creating issue
+type CreateIssueForm struct {
+ Title string `binding:"Required;MaxSize(255)"`
+ LabelIDs string `form:"label_ids"`
+ AssigneeIDs string `form:"assignee_ids"`
+ Ref string `form:"ref"`
+ MilestoneID int64
+ ProjectID int64
+ AssigneeID int64
+ Content string
+ Files []string
+ AllowMaintainerEdit bool
+}
+
+// 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)
+}
+
+// CreateCommentForm form for creating comment
+type CreateCommentForm struct {
+ Content string
+ Status string `binding:"OmitEmpty;In(reopen,close)"`
+ Files []string
+}
+
+// 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)
+}
+
+// ReactionForm form for adding and removing reaction
+type ReactionForm struct {
+ Content string `binding:"Required"`
+}
+
+// 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)
+}
+
+// IssueLockForm form for locking an issue
+type IssueLockForm struct {
+ Reason string `binding:"Required"`
+}
+
+// Validate validates the fields
+func (i *IssueLockForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetValidateContext(req)
+ return middleware.Validate(errs, ctx.Data, i, ctx.Locale)
+}
+
+// HasValidReason checks to make sure that the reason submitted in
+// the form matches any of the values in the config
+func (i IssueLockForm) HasValidReason() bool {
+ if strings.TrimSpace(i.Reason) == "" {
+ return true
+ }
+
+ for _, v := range setting.Repository.Issue.LockReasons {
+ if v == i.Reason {
+ return true
+ }
+ }
+
+ return false
+}
+
+// CreateProjectForm form for creating a project
+type CreateProjectForm struct {
+ Title string `binding:"Required;MaxSize(100)"`
+ Content string
+ TemplateType project_model.TemplateType
+ CardType project_model.CardType
+}
+
+// EditProjectColumnForm is a form for editing a project column
+type EditProjectColumnForm struct {
+ Title string `binding:"Required;MaxSize(100)"`
+ Sorting int8
+ Color string `binding:"MaxSize(7)"`
+}
+
+// CreateMilestoneForm form for creating milestone
+type CreateMilestoneForm struct {
+ Title string `binding:"Required;MaxSize(50)"`
+ Content string
+ Deadline string
+}
+
+// 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)
+}
+
+// CreateLabelForm form for creating label
+type CreateLabelForm struct {
+ ID int64
+ Title string `binding:"Required;MaxSize(50)" locale:"repo.issues.label_title"`
+ Exclusive bool `form:"exclusive"`
+ IsArchived bool `form:"is_archived"`
+ Description string `binding:"MaxSize(200)" locale:"repo.issues.label_description"`
+ Color string `binding:"Required;MaxSize(7)" locale:"repo.issues.label_color"`
+}
+
+// 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)
+}
+
+// InitializeLabelsForm form for initializing labels
+type InitializeLabelsForm struct {
+ TemplateName string `binding:"Required"`
+}
+
+// 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)
+}
+
+// MergePullRequestForm form for merging Pull Request
+// swagger:model MergePullRequestOption
+type MergePullRequestForm struct {
+ // required: true
+ // enum: ["merge", "rebase", "rebase-merge", "squash", "fast-forward-only", "manually-merged"]
+ Do string `binding:"Required;In(merge,rebase,rebase-merge,squash,fast-forward-only,manually-merged)"`
+ MergeTitleField string
+ MergeMessageField string
+ MergeCommitID string // only used for manually-merged
+ HeadCommitID string `json:"head_commit_id,omitempty"`
+ ForceMerge bool `json:"force_merge,omitempty"`
+ MergeWhenChecksSucceed bool `json:"merge_when_checks_succeed,omitempty"`
+ DeleteBranchAfterMerge bool `json:"delete_branch_after_merge,omitempty"`
+}
+
+// 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)
+}
+
+// CodeCommentForm form for adding code comments for PRs
+type CodeCommentForm struct {
+ Origin string `binding:"Required;In(timeline,diff)"`
+ Content string `binding:"Required"`
+ Side string `binding:"Required;In(previous,proposed)"`
+ Line int64
+ TreePath string `form:"path" binding:"Required"`
+ SingleReview bool `form:"single_review"`
+ Reply int64 `form:"reply"`
+ LatestCommitID string
+ Files []string
+}
+
+// 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)
+}
+
+// SubmitReviewForm for submitting a finished code review
+type SubmitReviewForm struct {
+ Content string
+ Type string
+ CommitID string
+ Files []string
+}
+
+// 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)
+}
+
+// ReviewType will return the corresponding ReviewType for type
+func (f SubmitReviewForm) ReviewType() issues_model.ReviewType {
+ switch f.Type {
+ case "approve":
+ return issues_model.ReviewTypeApprove
+ case "comment":
+ return issues_model.ReviewTypeComment
+ case "reject":
+ return issues_model.ReviewTypeReject
+ case "":
+ return issues_model.ReviewTypeComment // default to comment when doing quick-submit (Ctrl+Enter) on the review form
+ default:
+ return issues_model.ReviewTypeUnknown
+ }
+}
+
+// HasEmptyContent checks if the content of the review form is empty.
+func (f SubmitReviewForm) HasEmptyContent() bool {
+ reviewType := f.ReviewType()
+
+ return (reviewType == issues_model.ReviewTypeComment || reviewType == issues_model.ReviewTypeReject) &&
+ len(strings.TrimSpace(f.Content)) == 0
+}
+
+// DismissReviewForm for dismissing stale review by repo admin
+type DismissReviewForm struct {
+ ReviewID int64 `binding:"Required"`
+ Message string
+}
+
+// UpdateAllowEditsForm form for changing if PR allows edits from maintainers
+type UpdateAllowEditsForm struct {
+ AllowMaintainerEdit bool
+}
+
+// __________ .__
+// \______ \ ____ | | ____ _____ ______ ____
+// | _// __ \| | _/ __ \\__ \ / ___// __ \
+// | | \ ___/| |_\ ___/ / __ \_\___ \\ ___/
+// |____|_ /\___ >____/\___ >____ /____ >\___ >
+// \/ \/ \/ \/ \/ \/
+
+// NewReleaseForm form for creating release
+type NewReleaseForm struct {
+ TagName string `binding:"Required;GitRefName;MaxSize(255)"`
+ Target string `form:"tag_target" binding:"Required;MaxSize(255)"`
+ Title string `binding:"MaxSize(255)"`
+ Content string
+ Draft string
+ TagOnly string
+ Prerelease bool
+ AddTagMsg bool
+ HideArchiveLinks bool
+ Files []string
+}
+
+// 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)
+}
+
+// EditReleaseForm form for changing release
+type EditReleaseForm struct {
+ Title string `form:"title" binding:"Required;MaxSize(255)"`
+ Content string `form:"content"`
+ Draft string `form:"draft"`
+ Prerelease bool `form:"prerelease"`
+ HideArchiveLinks bool
+ Files []string
+}
+
+// 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)
+}
+
+// __ __.__ __ .__
+// / \ / \__| | _|__|
+// \ \/\/ / | |/ / |
+// \ /| | <| |
+// \__/\ / |__|__|_ \__|
+// \/ \/
+
+// NewWikiForm form for creating wiki
+type NewWikiForm struct {
+ Title string `binding:"Required"`
+ Content string `binding:"Required"`
+ Message string
+}
+
+// Validate validates the fields
+// 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)
+}
+
+// ___________ .___.__ __
+// \_ _____/ __| _/|__|/ |_
+// | __)_ / __ | | \ __\
+// | \/ /_/ | | || |
+// /_______ /\____ | |__||__|
+// \/ \/
+
+// EditRepoFileForm form for changing repository file
+type EditRepoFileForm struct {
+ TreePath string `binding:"Required;MaxSize(500)"`
+ Content string
+ CommitSummary string `binding:"MaxSize(100)"`
+ CommitMessage string
+ CommitChoice string `binding:"Required;MaxSize(50)"`
+ NewBranchName string `binding:"GitRefName;MaxSize(100)"`
+ LastCommit string
+ CommitMailID int64 `binding:"Required"`
+ Signoff bool
+}
+
+// Validate validates the fields
+func (f *EditRepoFileForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetValidateContext(req)
+ return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// EditPreviewDiffForm form for changing preview diff
+type EditPreviewDiffForm struct {
+ Content string
+}
+
+// Validate validates the fields
+func (f *EditPreviewDiffForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetValidateContext(req)
+ return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// _________ .__ __________.__ __
+// \_ ___ \| |__ __________________ ___.__. \______ \__| ____ | | __
+// / \ \/| | \_/ __ \_ __ \_ __ < | | | ___/ |/ ___\| |/ /
+// \ \___| Y \ ___/| | \/| | \/\___ | | | | \ \___| <
+// \______ /___| /\___ >__| |__| / ____| |____| |__|\___ >__|_ \
+// \/ \/ \/ \/ \/ \/
+
+// CherryPickForm form for changing repository file
+type CherryPickForm struct {
+ CommitSummary string `binding:"MaxSize(100)"`
+ CommitMessage string
+ CommitChoice string `binding:"Required;MaxSize(50)"`
+ NewBranchName string `binding:"GitRefName;MaxSize(100)"`
+ LastCommit string
+ CommitMailID int64 `binding:"Required"`
+ Revert bool
+ Signoff bool
+}
+
+// Validate validates the fields
+func (f *CherryPickForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetValidateContext(req)
+ return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// ____ ___ .__ .___
+// | | \______ | | _________ __| _/
+// | | /\____ \| | / _ \__ \ / __ |
+// | | / | |_> > |_( <_> ) __ \_/ /_/ |
+// |______/ | __/|____/\____(____ /\____ |
+// |__| \/ \/
+//
+
+// UploadRepoFileForm form for uploading repository file
+type UploadRepoFileForm struct {
+ TreePath string `binding:"MaxSize(500)"`
+ CommitSummary string `binding:"MaxSize(100)"`
+ CommitMessage string
+ CommitChoice string `binding:"Required;MaxSize(50)"`
+ NewBranchName string `binding:"GitRefName;MaxSize(100)"`
+ Files []string
+ CommitMailID int64 `binding:"Required"`
+ Signoff bool
+}
+
+// Validate validates the fields
+func (f *UploadRepoFileForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetValidateContext(req)
+ return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// RemoveUploadFileForm form for removing uploaded file
+type RemoveUploadFileForm struct {
+ File string `binding:"Required;MaxSize(50)"`
+}
+
+// Validate validates the fields
+func (f *RemoveUploadFileForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetValidateContext(req)
+ return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// ________ .__ __
+// \______ \ ____ | | _____/ |_ ____
+// | | \_/ __ \| | _/ __ \ __\/ __ \
+// | ` \ ___/| |_\ ___/| | \ ___/
+// /_______ /\___ >____/\___ >__| \___ >
+// \/ \/ \/ \/
+
+// DeleteRepoFileForm form for deleting repository file
+type DeleteRepoFileForm struct {
+ CommitSummary string `binding:"MaxSize(100)"`
+ CommitMessage string
+ CommitChoice string `binding:"Required;MaxSize(50)"`
+ NewBranchName string `binding:"GitRefName;MaxSize(100)"`
+ LastCommit string
+ CommitMailID int64 `binding:"Required"`
+ Signoff bool
+}
+
+// Validate validates the fields
+func (f *DeleteRepoFileForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetValidateContext(req)
+ return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// ___________.__ ___________ __
+// \__ ___/|__| _____ ____ \__ ___/___________ ____ | | __ ___________
+// | | | |/ \_/ __ \ | | \_ __ \__ \ _/ ___\| |/ // __ \_ __ \
+// | | | | Y Y \ ___/ | | | | \// __ \\ \___| <\ ___/| | \/
+// |____| |__|__|_| /\___ > |____| |__| (____ /\___ >__|_ \\___ >__|
+// \/ \/ \/ \/ \/ \/
+
+// AddTimeManuallyForm form that adds spent time manually.
+type AddTimeManuallyForm struct {
+ Hours int `binding:"Range(0,1000)"`
+ Minutes int `binding:"Range(0,1000)"`
+}
+
+// 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)
+}
+
+// SaveTopicForm form for save topics for repository
+type SaveTopicForm struct {
+ Topics []string `binding:"topics;Required;"`
+}
+
+// DeadlineForm hold the validation rules for deadlines
+type DeadlineForm struct {
+ DateString string `form:"date" binding:"Required;Size(10)"`
+}
+
+// Validate validates the fields
+func (f *DeadlineForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetValidateContext(req)
+ return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
+}
diff --git a/services/forms/repo_form_test.go b/services/forms/repo_form_test.go
new file mode 100644
index 0000000..2c5a8e2
--- /dev/null
+++ b/services/forms/repo_form_test.go
@@ -0,0 +1,64 @@
+// Copyright 2018 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package forms
+
+import (
+ "testing"
+
+ "code.gitea.io/gitea/modules/setting"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestSubmitReviewForm_IsEmpty(t *testing.T) {
+ cases := []struct {
+ form SubmitReviewForm
+ expected bool
+ }{
+ // Approved PR with a comment shouldn't count as empty
+ {SubmitReviewForm{Type: "approve", Content: "Awesome"}, false},
+
+ // Approved PR without a comment shouldn't count as empty
+ {SubmitReviewForm{Type: "approve", Content: ""}, false},
+
+ // Rejected PR without a comment should count as empty
+ {SubmitReviewForm{Type: "reject", Content: ""}, true},
+
+ // Rejected PR with a comment shouldn't count as empty
+ {SubmitReviewForm{Type: "reject", Content: "Awesome"}, false},
+
+ // Comment review on a PR with a comment shouldn't count as empty
+ {SubmitReviewForm{Type: "comment", Content: "Awesome"}, false},
+
+ // Comment review on a PR without a comment should count as empty
+ {SubmitReviewForm{Type: "comment", Content: ""}, true},
+ }
+
+ for _, v := range cases {
+ assert.Equal(t, v.expected, v.form.HasEmptyContent())
+ }
+}
+
+func TestIssueLock_HasValidReason(t *testing.T) {
+ // Init settings
+ _ = setting.Repository
+
+ cases := []struct {
+ form IssueLockForm
+ expected bool
+ }{
+ {IssueLockForm{""}, true}, // an empty reason is accepted
+ {IssueLockForm{"Off-topic"}, true},
+ {IssueLockForm{"Too heated"}, true},
+ {IssueLockForm{"Spam"}, true},
+ {IssueLockForm{"Resolved"}, true},
+
+ {IssueLockForm{"ZZZZ"}, false},
+ {IssueLockForm{"I want to lock this issue"}, false},
+ }
+
+ for _, v := range cases {
+ assert.Equal(t, v.expected, v.form.HasValidReason())
+ }
+}
diff --git a/services/forms/repo_tag_form.go b/services/forms/repo_tag_form.go
new file mode 100644
index 0000000..0135684
--- /dev/null
+++ b/services/forms/repo_tag_form.go
@@ -0,0 +1,26 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package forms
+
+import (
+ "net/http"
+
+ "code.gitea.io/gitea/modules/web/middleware"
+ "code.gitea.io/gitea/services/context"
+
+ "gitea.com/go-chi/binding"
+)
+
+// ProtectTagForm form for changing protected tag settings
+type ProtectTagForm struct {
+ NamePattern string `binding:"Required;GlobOrRegexPattern"`
+ AllowlistUsers string
+ AllowlistTeams string
+}
+
+// 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)
+}
diff --git a/services/forms/runner.go b/services/forms/runner.go
new file mode 100644
index 0000000..6abfc66
--- /dev/null
+++ b/services/forms/runner.go
@@ -0,0 +1,24 @@
+// Copyright 2022 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package forms
+
+import (
+ "net/http"
+
+ "code.gitea.io/gitea/modules/web/middleware"
+ "code.gitea.io/gitea/services/context"
+
+ "gitea.com/go-chi/binding"
+)
+
+// EditRunnerForm form for admin to create runner
+type EditRunnerForm struct {
+ Description string
+}
+
+// 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)
+}
diff --git a/services/forms/user_form.go b/services/forms/user_form.go
new file mode 100644
index 0000000..cc93b27
--- /dev/null
+++ b/services/forms/user_form.go
@@ -0,0 +1,455 @@
+// Copyright 2014 The Gogs Authors. All rights reserved.
+// Copyright 2018 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package forms
+
+import (
+ "mime/multipart"
+ "net/http"
+ "strings"
+
+ auth_model "code.gitea.io/gitea/models/auth"
+ user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/web/middleware"
+ "code.gitea.io/gitea/services/context"
+
+ "gitea.com/go-chi/binding"
+)
+
+// InstallForm form for installation page
+type InstallForm struct {
+ DbType string `binding:"Required"`
+ DbHost string
+ DbUser string
+ DbPasswd string
+ DbName string
+ SSLMode string
+ DbPath string
+ DbSchema string
+
+ AppName string `binding:"Required" locale:"install.app_name"`
+ AppSlogan string
+ RepoRootPath string `binding:"Required"`
+ LFSRootPath string
+ RunUser string `binding:"Required"`
+ Domain string `binding:"Required"`
+ SSHPort int
+ HTTPPort string `binding:"Required"`
+ AppURL string `binding:"Required"`
+ LogRootPath string `binding:"Required"`
+
+ SMTPAddr string
+ SMTPPort string
+ SMTPFrom string
+ SMTPUser string `binding:"OmitEmpty;MaxSize(254)" locale:"install.mailer_user"`
+ SMTPPasswd string
+ RegisterConfirm bool
+ MailNotify bool
+
+ OfflineMode bool
+ DisableGravatar bool
+ EnableFederatedAvatar bool
+ EnableOpenIDSignIn bool
+ EnableOpenIDSignUp bool
+ DisableRegistration bool
+ AllowOnlyExternalRegistration bool
+ EnableCaptcha bool
+ RequireSignInView bool
+ DefaultKeepEmailPrivate bool
+ DefaultAllowCreateOrganization bool
+ DefaultEnableTimetracking bool
+ EnableUpdateChecker bool
+ NoReplyAddress string
+
+ PasswordAlgorithm string
+
+ AdminName string `binding:"OmitEmpty;Username;MaxSize(30)" locale:"install.admin_name"`
+ AdminPasswd string `binding:"OmitEmpty;MaxSize(255)" locale:"install.admin_password"`
+ AdminConfirmPasswd string
+ AdminEmail string `binding:"OmitEmpty;MinSize(3);MaxSize(254);Include(@)" locale:"install.admin_email"`
+
+ // ReinstallConfirmFirst we can not use 1/2/3 or A/B/C here, there is a framework bug, can not parse "reinstall_confirm_1" or "reinstall_confirm_a"
+ ReinstallConfirmFirst bool
+ ReinstallConfirmSecond bool
+ ReinstallConfirmThird bool
+}
+
+// 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)
+}
+
+// _____ ____ _________________ ___
+// / _ \ | | \__ ___/ | \
+// / /_\ \| | / | | / ~ \
+// / | \ | / | | \ Y /
+// \____|__ /______/ |____| \___|_ /
+// \/ \/
+
+// RegisterForm form for registering
+type RegisterForm struct {
+ UserName string `binding:"Required;Username;MaxSize(40)"`
+ Email string `binding:"Required;MaxSize(254)"`
+ Password string `binding:"MaxSize(255)"`
+ Retype string
+}
+
+// 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)
+}
+
+// IsEmailDomainAllowed validates that the email address
+// provided by the user matches what has been configured .
+// The email is marked as allowed if it matches any of the
+// domains in the whitelist or if it doesn't match any of
+// domains in the blocklist, if any such list is not empty.
+func (f *RegisterForm) IsEmailDomainAllowed() bool {
+ return user_model.IsEmailDomainAllowed(f.Email)
+}
+
+// MustChangePasswordForm form for updating your password after account creation
+// by an admin
+type MustChangePasswordForm struct {
+ Password string `binding:"Required;MaxSize(255)"`
+ Retype string
+}
+
+// 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)
+}
+
+// SignInForm form for signing in with user/password
+type SignInForm struct {
+ UserName string `binding:"Required;MaxSize(254)"`
+ // TODO remove required from password for SecondFactorAuthentication
+ Password string `binding:"Required;MaxSize(255)"`
+ Remember bool
+}
+
+// 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)
+}
+
+// AuthorizationForm form for authorizing oauth2 clients
+type AuthorizationForm struct {
+ ResponseType string `binding:"Required;In(code)"`
+ ClientID string `binding:"Required"`
+ RedirectURI string
+ State string
+ Scope string
+ Nonce string
+
+ // PKCE support
+ CodeChallengeMethod string // S256, plain
+ CodeChallenge string
+}
+
+// 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)
+}
+
+// GrantApplicationForm form for authorizing oauth2 clients
+type GrantApplicationForm struct {
+ ClientID string `binding:"Required"`
+ Granted bool
+ RedirectURI string
+ State string
+ Scope string
+ Nonce string
+}
+
+// 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)
+}
+
+// AccessTokenForm for issuing access tokens from authorization codes or refresh tokens
+type AccessTokenForm struct {
+ GrantType string `json:"grant_type"`
+ ClientID string `json:"client_id"`
+ ClientSecret string `json:"client_secret"`
+ RedirectURI string `json:"redirect_uri"`
+ Code string `json:"code"`
+ RefreshToken string `json:"refresh_token"`
+
+ // PKCE support
+ CodeVerifier string `json:"code_verifier"`
+}
+
+// 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)
+}
+
+// IntrospectTokenForm for introspecting tokens
+type IntrospectTokenForm struct {
+ Token string `json:"token"`
+}
+
+// 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)
+}
+
+// __________________________________________.___ _______ ________ _________
+// / _____/\_ _____/\__ ___/\__ ___/| |\ \ / _____/ / _____/
+// \_____ \ | __)_ | | | | | |/ | \/ \ ___ \_____ \
+// / \ | \ | | | | | / | \ \_\ \/ \
+// /_______ //_______ / |____| |____| |___\____|__ /\______ /_______ /
+// \/ \/ \/ \/ \/
+
+// UpdateProfileForm form for updating profile
+type UpdateProfileForm struct {
+ Name string `binding:"Username;MaxSize(40)"`
+ FullName string `binding:"MaxSize(100)"`
+ KeepEmailPrivate bool
+ Website string `binding:"ValidSiteUrl;MaxSize(255)"`
+ Location string `binding:"MaxSize(50)"`
+ Pronouns string `binding:"MaxSize(50)"`
+ Biography string `binding:"MaxSize(255)"`
+ Visibility structs.VisibleType
+ KeepActivityPrivate bool
+}
+
+// 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)
+}
+
+// UpdateLanguageForm form for updating profile
+type UpdateLanguageForm struct {
+ Language string
+}
+
+// UpdateHintsForm form for updating user hint settings
+type UpdateHintsForm struct {
+ EnableRepoUnitHints bool
+}
+
+// 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)
+}
+
+// Avatar types
+const (
+ AvatarLocal string = "local"
+ AvatarByMail string = "bymail"
+)
+
+// AvatarForm form for changing avatar
+type AvatarForm struct {
+ Source string
+ Avatar *multipart.FileHeader
+ Gravatar string `binding:"OmitEmpty;Email;MaxSize(254)"`
+ Federavatar bool
+}
+
+// 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)
+}
+
+// AddEmailForm form for adding new email
+type AddEmailForm struct {
+ Email string `binding:"Required;Email;MaxSize(254)"`
+}
+
+// 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)
+}
+
+// UpdateThemeForm form for updating a users' theme
+type UpdateThemeForm struct {
+ Theme string `binding:"Required;MaxSize(64)"`
+}
+
+// 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)
+}
+
+// IsThemeExists checks if the theme is a theme available in the config.
+func (f UpdateThemeForm) IsThemeExists() bool {
+ var exists bool
+
+ for _, v := range setting.UI.Themes {
+ if strings.EqualFold(v, f.Theme) {
+ exists = true
+ break
+ }
+ }
+
+ return exists
+}
+
+// ChangePasswordForm form for changing password
+type ChangePasswordForm struct {
+ OldPassword string `form:"old_password" binding:"MaxSize(255)"`
+ Password string `form:"password" binding:"Required;MaxSize(255)"`
+ Retype string `form:"retype"`
+}
+
+// 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)
+}
+
+// AddOpenIDForm is for changing openid uri
+type AddOpenIDForm struct {
+ Openid string `binding:"Required;MaxSize(256)"`
+}
+
+// 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)
+}
+
+// AddKeyForm form for adding SSH/GPG key
+type AddKeyForm struct {
+ Type string `binding:"OmitEmpty"`
+ Title string `binding:"Required;MaxSize(50)"`
+ Content string `binding:"Required"`
+ Signature string `binding:"OmitEmpty"`
+ KeyID string `binding:"OmitEmpty"`
+ Fingerprint string `binding:"OmitEmpty"`
+ IsWritable bool
+}
+
+// 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)
+}
+
+// AddSecretForm for adding secrets
+type AddSecretForm struct {
+ Name string `binding:"Required;MaxSize(255)"`
+ Data string `binding:"Required;MaxSize(65535)"`
+}
+
+// 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)
+}
+
+type EditVariableForm struct {
+ Name string `binding:"Required;MaxSize(255)"`
+ Data string `binding:"Required;MaxSize(65535)"`
+}
+
+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)
+}
+
+// NewAccessTokenForm form for creating access token
+type NewAccessTokenForm struct {
+ Name string `binding:"Required;MaxSize(255)" locale:"settings.token_name"`
+ Scope []string
+}
+
+// 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)
+}
+
+func (f *NewAccessTokenForm) GetScope() (auth_model.AccessTokenScope, error) {
+ scope := strings.Join(f.Scope, ",")
+ s, err := auth_model.AccessTokenScope(scope).Normalize()
+ return s, err
+}
+
+// EditOAuth2ApplicationForm form for editing oauth2 applications
+type EditOAuth2ApplicationForm struct {
+ Name string `binding:"Required;MaxSize(255)" form:"application_name"`
+ RedirectURIs string `binding:"Required" form:"redirect_uris"`
+ ConfidentialClient bool `form:"confidential_client"`
+}
+
+// Validate validates the fields
+func (f *EditOAuth2ApplicationForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetValidateContext(req)
+ return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// TwoFactorAuthForm for logging in with 2FA token.
+type TwoFactorAuthForm struct {
+ Passcode string `binding:"Required"`
+}
+
+// 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)
+}
+
+// TwoFactorScratchAuthForm for logging in with 2FA scratch token.
+type TwoFactorScratchAuthForm struct {
+ Token string `binding:"Required"`
+}
+
+// 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)
+}
+
+// WebauthnRegistrationForm for reserving an WebAuthn name
+type WebauthnRegistrationForm struct {
+ Name string `binding:"Required"`
+}
+
+// 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)
+}
+
+// WebauthnDeleteForm for deleting WebAuthn keys
+type WebauthnDeleteForm struct {
+ ID int64 `binding:"Required"`
+}
+
+// Validate validates the fields
+func (f *WebauthnDeleteForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
+ ctx := context.GetValidateContext(req)
+ return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
+}
+
+// PackageSettingForm form for package settings
+type PackageSettingForm struct {
+ Action string
+ RepoID int64 `form:"repo_id"`
+}
+
+// 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)
+}
diff --git a/services/forms/user_form_auth_openid.go b/services/forms/user_form_auth_openid.go
new file mode 100644
index 0000000..ca1c77e
--- /dev/null
+++ b/services/forms/user_form_auth_openid.go
@@ -0,0 +1,49 @@
+// Copyright 2017 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package forms
+
+import (
+ "net/http"
+
+ "code.gitea.io/gitea/modules/web/middleware"
+ "code.gitea.io/gitea/services/context"
+
+ "gitea.com/go-chi/binding"
+)
+
+// SignInOpenIDForm form for signing in with OpenID
+type SignInOpenIDForm struct {
+ Openid string `binding:"Required;MaxSize(256)"`
+ Remember bool
+}
+
+// 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)
+}
+
+// SignUpOpenIDForm form for signin up with OpenID
+type SignUpOpenIDForm struct {
+ UserName string `binding:"Required;Username;MaxSize(40)"`
+ Email string `binding:"Required;Email;MaxSize(254)"`
+}
+
+// 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)
+}
+
+// ConnectOpenIDForm form for connecting an existing account to an OpenID URI
+type ConnectOpenIDForm struct {
+ UserName string `binding:"Required;MaxSize(254)"`
+ Password string `binding:"Required;MaxSize(255)"`
+}
+
+// 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)
+}
diff --git a/services/forms/user_form_hidden_comments.go b/services/forms/user_form_hidden_comments.go
new file mode 100644
index 0000000..b9677c1
--- /dev/null
+++ b/services/forms/user_form_hidden_comments.go
@@ -0,0 +1,104 @@
+// Copyright 2021 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package forms
+
+import (
+ "math/big"
+
+ issues_model "code.gitea.io/gitea/models/issues"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/services/context"
+)
+
+type hiddenCommentTypeGroupsType map[string][]issues_model.CommentType
+
+// hiddenCommentTypeGroups maps the group names to comment types, these group names comes from the Web UI (appearance.tmpl)
+var hiddenCommentTypeGroups = hiddenCommentTypeGroupsType{
+ "reference": {
+ /*3*/ issues_model.CommentTypeIssueRef,
+ /*4*/ issues_model.CommentTypeCommitRef,
+ /*5*/ issues_model.CommentTypeCommentRef,
+ /*6*/ issues_model.CommentTypePullRef,
+ },
+ "label": {
+ /*7*/ issues_model.CommentTypeLabel,
+ },
+ "milestone": {
+ /*8*/ issues_model.CommentTypeMilestone,
+ },
+ "assignee": {
+ /*9*/ issues_model.CommentTypeAssignees,
+ },
+ "title": {
+ /*10*/ issues_model.CommentTypeChangeTitle,
+ },
+ "branch": {
+ /*11*/ issues_model.CommentTypeDeleteBranch,
+ /*25*/ issues_model.CommentTypeChangeTargetBranch,
+ },
+ "time_tracking": {
+ /*12*/ issues_model.CommentTypeStartTracking,
+ /*13*/ issues_model.CommentTypeStopTracking,
+ /*14*/ issues_model.CommentTypeAddTimeManual,
+ /*15*/ issues_model.CommentTypeCancelTracking,
+ /*26*/ issues_model.CommentTypeDeleteTimeManual,
+ },
+ "deadline": {
+ /*16*/ issues_model.CommentTypeAddedDeadline,
+ /*17*/ issues_model.CommentTypeModifiedDeadline,
+ /*18*/ issues_model.CommentTypeRemovedDeadline,
+ },
+ "dependency": {
+ /*19*/ issues_model.CommentTypeAddDependency,
+ /*20*/ issues_model.CommentTypeRemoveDependency,
+ },
+ "lock": {
+ /*23*/ issues_model.CommentTypeLock,
+ /*24*/ issues_model.CommentTypeUnlock,
+ },
+ "review_request": {
+ /*27*/ issues_model.CommentTypeReviewRequest,
+ },
+ "pull_request_push": {
+ /*29*/ issues_model.CommentTypePullRequestPush,
+ },
+ "project": {
+ /*30*/ issues_model.CommentTypeProject,
+ /*31*/ issues_model.CommentTypeProjectColumn,
+ },
+ "issue_ref": {
+ /*33*/ issues_model.CommentTypeChangeIssueRef,
+ },
+}
+
+// UserHiddenCommentTypesFromRequest parse the form to hidden comment types bitset
+func UserHiddenCommentTypesFromRequest(ctx *context.Context) *big.Int {
+ bitset := new(big.Int)
+ for group, commentTypes := range hiddenCommentTypeGroups {
+ if ctx.FormBool(group) {
+ for _, commentType := range commentTypes {
+ bitset = bitset.SetBit(bitset, int(commentType), 1)
+ }
+ }
+ }
+ return bitset
+}
+
+// IsUserHiddenCommentTypeGroupChecked check whether a hidden comment type group is "enabled" (checked on UI)
+func IsUserHiddenCommentTypeGroupChecked(group string, hiddenCommentTypes *big.Int) (ret bool) {
+ commentTypes, ok := hiddenCommentTypeGroups[group]
+ if !ok {
+ log.Critical("the group map for hidden comment types is out of sync, unknown group: %v", group)
+ return false
+ }
+ if hiddenCommentTypes == nil {
+ return false
+ }
+ for _, commentType := range commentTypes {
+ if hiddenCommentTypes.Bit(int(commentType)) == 1 {
+ return true
+ }
+ }
+ return false
+}
diff --git a/services/forms/user_form_test.go b/services/forms/user_form_test.go
new file mode 100644
index 0000000..6605018
--- /dev/null
+++ b/services/forms/user_form_test.go
@@ -0,0 +1,131 @@
+// Copyright 2018 The Gogs Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package forms
+
+import (
+ "strconv"
+ "testing"
+
+ auth_model "code.gitea.io/gitea/models/auth"
+ "code.gitea.io/gitea/modules/setting"
+
+ "github.com/gobwas/glob"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestRegisterForm_IsDomainAllowed_Empty(t *testing.T) {
+ oldService := setting.Service
+ defer func() {
+ setting.Service = oldService
+ }()
+
+ setting.Service.EmailDomainAllowList = nil
+
+ form := RegisterForm{}
+
+ assert.True(t, form.IsEmailDomainAllowed())
+}
+
+func TestRegisterForm_IsDomainAllowed_InvalidEmail(t *testing.T) {
+ oldService := setting.Service
+ defer func() {
+ setting.Service = oldService
+ }()
+
+ setting.Service.EmailDomainAllowList = []glob.Glob{glob.MustCompile("gitea.io")}
+
+ tt := []struct {
+ email string
+ }{
+ {"invalid-email"},
+ {"gitea.io"},
+ }
+
+ for _, v := range tt {
+ form := RegisterForm{Email: v.email}
+
+ assert.False(t, form.IsEmailDomainAllowed())
+ }
+}
+
+func TestRegisterForm_IsDomainAllowed_AllowedEmail(t *testing.T) {
+ oldService := setting.Service
+ defer func() {
+ setting.Service = oldService
+ }()
+
+ setting.Service.EmailDomainAllowList = []glob.Glob{glob.MustCompile("gitea.io"), glob.MustCompile("*.allow")}
+
+ tt := []struct {
+ email string
+ valid bool
+ }{
+ {"security@gitea.io", true},
+ {"security@gITea.io", true},
+ {"invalid", false},
+ {"seee@example.com", false},
+
+ {"user@my.allow", true},
+ {"user@my.allow1", false},
+ }
+
+ for _, v := range tt {
+ form := RegisterForm{Email: v.email}
+
+ assert.Equal(t, v.valid, form.IsEmailDomainAllowed())
+ }
+}
+
+func TestRegisterForm_IsDomainAllowed_BlockedEmail(t *testing.T) {
+ oldService := setting.Service
+ defer func() {
+ setting.Service = oldService
+ }()
+
+ setting.Service.EmailDomainAllowList = nil
+ setting.Service.EmailDomainBlockList = []glob.Glob{glob.MustCompile("gitea.io"), glob.MustCompile("*.block")}
+
+ tt := []struct {
+ email string
+ valid bool
+ }{
+ {"security@gitea.io", false},
+ {"security@gitea.example", true},
+ {"invalid", true},
+
+ {"user@my.block", false},
+ {"user@my.block1", true},
+ }
+
+ for _, v := range tt {
+ form := RegisterForm{Email: v.email}
+
+ assert.Equal(t, v.valid, form.IsEmailDomainAllowed())
+ }
+}
+
+func TestNewAccessTokenForm_GetScope(t *testing.T) {
+ tests := []struct {
+ form NewAccessTokenForm
+ scope auth_model.AccessTokenScope
+ expectedErr error
+ }{
+ {
+ form: NewAccessTokenForm{Name: "test", Scope: []string{"read:repository"}},
+ scope: "read:repository",
+ },
+ {
+ form: NewAccessTokenForm{Name: "test", Scope: []string{"read:repository", "write:user"}},
+ scope: "read:repository,write:user",
+ },
+ }
+
+ for i, test := range tests {
+ t.Run(strconv.Itoa(i), func(t *testing.T) {
+ scope, err := test.form.GetScope()
+ assert.Equal(t, test.expectedErr, err)
+ assert.Equal(t, test.scope, scope)
+ })
+ }
+}