From dd136858f1ea40ad3c94191d647487fa4f31926c Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 18 Oct 2024 20:33:49 +0200 Subject: Adding upstream version 9.0.0. Signed-off-by: Daniel Baumann --- services/forms/admin.go | 74 +++ services/forms/auth_form.go | 92 ++++ services/forms/org.go | 76 +++ services/forms/package_form.go | 30 ++ services/forms/repo_branch_form.go | 38 ++ services/forms/repo_form.go | 751 ++++++++++++++++++++++++++++ services/forms/repo_form_test.go | 64 +++ services/forms/repo_tag_form.go | 26 + services/forms/runner.go | 24 + services/forms/user_form.go | 455 +++++++++++++++++ services/forms/user_form_auth_openid.go | 49 ++ services/forms/user_form_hidden_comments.go | 104 ++++ services/forms/user_form_test.go | 131 +++++ 13 files changed, 1914 insertions(+) create mode 100644 services/forms/admin.go create mode 100644 services/forms/auth_form.go create mode 100644 services/forms/org.go create mode 100644 services/forms/package_form.go create mode 100644 services/forms/repo_branch_form.go create mode 100644 services/forms/repo_form.go create mode 100644 services/forms/repo_form_test.go create mode 100644 services/forms/repo_tag_form.go create mode 100644 services/forms/runner.go create mode 100644 services/forms/user_form.go create mode 100644 services/forms/user_form_auth_openid.go create mode 100644 services/forms/user_form_hidden_comments.go create mode 100644 services/forms/user_form_test.go (limited to 'services/forms') 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) + }) + } +} -- cgit v1.2.3