From a98a836e76ce8c95a16c9e26065fd05384b67ce8 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 14 Jan 2025 09:53:34 +0800 Subject: [PATCH] Support public code/issue access for private repositories (#33127) Close #8649, close #639 (will add "anonymous access" in following PRs) --- models/perm/access/repo_permission.go | 47 ++++-- models/perm/access/repo_permission_test.go | 12 +- options/locale/locale_en-US.ini | 2 +- routers/web/repo/issue_poster.go | 10 +- routers/web/repo/setting/setting.go | 17 +- routers/web/web.go | 183 +++++++++++---------- services/context/permission.go | 74 +-------- services/forms/repo_form.go | 66 ++++---- templates/repo/settings/options.tmpl | 20 ++- 9 files changed, 218 insertions(+), 213 deletions(-) diff --git a/models/perm/access/repo_permission.go b/models/perm/access/repo_permission.go index 0ed116a1324..e00b7c5320f 100644 --- a/models/perm/access/repo_permission.go +++ b/models/perm/access/repo_permission.go @@ -175,10 +175,14 @@ func (p *Permission) LogString() string { return fmt.Sprintf(format, args...) } -func applyEveryoneRepoPermission(user *user_model.User, perm *Permission) { +func finalProcessRepoUnitPermission(user *user_model.User, perm *Permission) { if user == nil || user.ID <= 0 { + // for anonymous access, it could be: + // AccessMode is None or Read, units has repo units, unitModes is nil return } + + // apply everyone access permissions for _, u := range perm.units { if u.EveryoneAccessMode >= perm_model.AccessModeRead && u.EveryoneAccessMode > perm.everyoneAccessMode[u.Type] { if perm.everyoneAccessMode == nil { @@ -187,17 +191,40 @@ func applyEveryoneRepoPermission(user *user_model.User, perm *Permission) { perm.everyoneAccessMode[u.Type] = u.EveryoneAccessMode } } + + if perm.unitsMode == nil { + // if unitsMode is not set, then it means that the default p.AccessMode applies to all units + return + } + + // remove no permission units + origPermUnits := perm.units + perm.units = make([]*repo_model.RepoUnit, 0, len(perm.units)) + for _, u := range origPermUnits { + shouldKeep := false + for t := range perm.unitsMode { + if shouldKeep = u.Type == t; shouldKeep { + break + } + } + for t := range perm.everyoneAccessMode { + if shouldKeep = shouldKeep || u.Type == t; shouldKeep { + break + } + } + if shouldKeep { + perm.units = append(perm.units, u) + } + } } // GetUserRepoPermission returns the user permissions to the repository func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (perm Permission, err error) { defer func() { if err == nil { - applyEveryoneRepoPermission(user, &perm) - } - if log.IsTrace() { - log.Trace("Permission Loaded for user %-v in repo %-v, permissions: %-+v", user, repo, perm) + finalProcessRepoUnitPermission(user, &perm) } + log.Trace("Permission Loaded for user %-v in repo %-v, permissions: %-+v", user, repo, perm) }() if err = repo.LoadUnits(ctx); err != nil { @@ -294,16 +321,6 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use } } - // remove no permission units - perm.units = make([]*repo_model.RepoUnit, 0, len(repo.Units)) - for t := range perm.unitsMode { - for _, u := range repo.Units { - if u.Type == t { - perm.units = append(perm.units, u) - } - } - } - return perm, err } diff --git a/models/perm/access/repo_permission_test.go b/models/perm/access/repo_permission_test.go index 50070c43684..9862da06731 100644 --- a/models/perm/access/repo_permission_test.go +++ b/models/perm/access/repo_permission_test.go @@ -50,7 +50,7 @@ func TestApplyEveryoneRepoPermission(t *testing.T) { {Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead}, }, } - applyEveryoneRepoPermission(nil, &perm) + finalProcessRepoUnitPermission(nil, &perm) assert.False(t, perm.CanRead(unit.TypeWiki)) perm = Permission{ @@ -59,7 +59,7 @@ func TestApplyEveryoneRepoPermission(t *testing.T) { {Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead}, }, } - applyEveryoneRepoPermission(&user_model.User{ID: 0}, &perm) + finalProcessRepoUnitPermission(&user_model.User{ID: 0}, &perm) assert.False(t, perm.CanRead(unit.TypeWiki)) perm = Permission{ @@ -68,7 +68,7 @@ func TestApplyEveryoneRepoPermission(t *testing.T) { {Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead}, }, } - applyEveryoneRepoPermission(&user_model.User{ID: 1}, &perm) + finalProcessRepoUnitPermission(&user_model.User{ID: 1}, &perm) assert.True(t, perm.CanRead(unit.TypeWiki)) perm = Permission{ @@ -77,20 +77,22 @@ func TestApplyEveryoneRepoPermission(t *testing.T) { {Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead}, }, } - applyEveryoneRepoPermission(&user_model.User{ID: 1}, &perm) + finalProcessRepoUnitPermission(&user_model.User{ID: 1}, &perm) // it should work the same as "EveryoneAccessMode: none" because the default AccessMode should be applied to units assert.True(t, perm.CanWrite(unit.TypeWiki)) perm = Permission{ units: []*repo_model.RepoUnit{ + {Type: unit.TypeCode}, // will be removed {Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead}, }, unitsMode: map[unit.Type]perm_model.AccessMode{ unit.TypeWiki: perm_model.AccessModeWrite, }, } - applyEveryoneRepoPermission(&user_model.User{ID: 1}, &perm) + finalProcessRepoUnitPermission(&user_model.User{ID: 1}, &perm) assert.True(t, perm.CanWrite(unit.TypeWiki)) + assert.Len(t, perm.units, 1) } func TestUnitAccessMode(t *testing.T) { diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 93155caa109..d336b6a700d 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -2158,7 +2158,7 @@ settings.advanced_settings = Advanced Settings settings.wiki_desc = Enable Repository Wiki settings.use_internal_wiki = Use Built-In Wiki settings.default_wiki_branch_name = Default Wiki Branch Name -settings.default_wiki_everyone_access = Default Access Permission for signed-in users: +settings.default_permission_everyone_access = Default access permission for all signed-in users: settings.failed_to_change_default_wiki_branch = Failed to change the default wiki branch. settings.use_external_wiki = Use External Wiki settings.external_wiki_url = External Wiki URL diff --git a/routers/web/repo/issue_poster.go b/routers/web/repo/issue_poster.go index 91ef947cb46..07059b9b7bf 100644 --- a/routers/web/repo/issue_poster.go +++ b/routers/web/repo/issue_poster.go @@ -26,13 +26,9 @@ type userSearchResponse struct { Results []*userSearchInfo `json:"results"` } -// IssuePosters get posters for current repo's issues/pull requests -func IssuePosters(ctx *context.Context) { - issuePosters(ctx, false) -} - -func PullPosters(ctx *context.Context) { - issuePosters(ctx, true) +func IssuePullPosters(ctx *context.Context) { + isPullList := ctx.PathParam("type") == "pulls" + issuePosters(ctx, isPullList) } func issuePosters(ctx *context.Context, isPullList bool) { diff --git a/routers/web/repo/setting/setting.go b/routers/web/repo/setting/setting.go index 7399c681e2e..5b34fc60da5 100644 --- a/routers/web/repo/setting/setting.go +++ b/routers/web/repo/setting/setting.go @@ -49,6 +49,15 @@ const ( tplDeployKeys templates.TplName = "repo/settings/deploy_keys" ) +func parseEveryoneAccessMode(permission string, allowed ...perm.AccessMode) perm.AccessMode { + // if site admin forces repositories to be private, then do not allow any other access mode, + // otherwise the "force private" setting would be bypassed + if setting.Repository.ForcePrivate { + return perm.AccessModeNone + } + return perm.ParseAccessMode(permission, allowed...) +} + // SettingsCtxData is a middleware that sets all the general context data for the // settings template. func SettingsCtxData(ctx *context.Context) { @@ -447,8 +456,9 @@ func SettingsPost(ctx *context.Context) { if form.EnableCode && !unit_model.TypeCode.UnitGlobalDisabled() { units = append(units, repo_model.RepoUnit{ - RepoID: repo.ID, - Type: unit_model.TypeCode, + RepoID: repo.ID, + Type: unit_model.TypeCode, + EveryoneAccessMode: parseEveryoneAccessMode(form.DefaultCodeEveryoneAccess, perm.AccessModeNone, perm.AccessModeRead), }) } else if !unit_model.TypeCode.UnitGlobalDisabled() { deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeCode) @@ -474,7 +484,7 @@ func SettingsPost(ctx *context.Context) { RepoID: repo.ID, Type: unit_model.TypeWiki, Config: new(repo_model.UnitConfig), - EveryoneAccessMode: perm.ParseAccessMode(form.DefaultWikiEveryoneAccess, perm.AccessModeNone, perm.AccessModeRead, perm.AccessModeWrite), + EveryoneAccessMode: parseEveryoneAccessMode(form.DefaultWikiEveryoneAccess, perm.AccessModeNone, perm.AccessModeRead, perm.AccessModeWrite), }) deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalWiki) } else { @@ -524,6 +534,7 @@ func SettingsPost(ctx *context.Context) { AllowOnlyContributorsToTrackTime: form.AllowOnlyContributorsToTrackTime, EnableDependencies: form.EnableIssueDependencies, }, + EveryoneAccessMode: parseEveryoneAccessMode(form.DefaultIssuesEveryoneAccess, perm.AccessModeNone, perm.AccessModeRead), }) deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalTracker) } else { diff --git a/routers/web/web.go b/routers/web/web.go index 2f7ee8ade76..d3d0360396d 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -101,7 +101,7 @@ func buildAuthGroup() *auth_service.Group { group.Add(&auth_service.Basic{}) // FIXME: this should be removed and only applied in download and git/lfs routers if setting.Service.EnableReverseProxyAuth { - group.Add(&auth_service.ReverseProxy{}) // reverseproxy should before Session, otherwise the header will be ignored if user has login + group.Add(&auth_service.ReverseProxy{}) // reverse-proxy should before Session, otherwise the header will be ignored if user has login } group.Add(&auth_service.Session{}) @@ -816,21 +816,24 @@ func registerRoutes(m *web.Router) { m.Post("/{username}", reqSignIn, context.UserAssignmentWeb(), user.Action) reqRepoAdmin := context.RequireRepoAdmin() - reqRepoCodeWriter := context.RequireRepoWriter(unit.TypeCode) + reqRepoCodeWriter := context.RequireUnitWriter(unit.TypeCode) canEnableEditor := context.CanEnableEditor() - reqRepoCodeReader := context.RequireRepoReader(unit.TypeCode) - reqRepoReleaseWriter := context.RequireRepoWriter(unit.TypeReleases) - reqRepoReleaseReader := context.RequireRepoReader(unit.TypeReleases) - reqRepoWikiReader := context.RequireRepoReader(unit.TypeWiki) - reqRepoWikiWriter := context.RequireRepoWriter(unit.TypeWiki) - reqRepoIssueReader := context.RequireRepoReader(unit.TypeIssues) - reqRepoPullsReader := context.RequireRepoReader(unit.TypePullRequests) - reqRepoIssuesOrPullsWriter := context.RequireRepoWriterOr(unit.TypeIssues, unit.TypePullRequests) - reqRepoIssuesOrPullsReader := context.RequireRepoReaderOr(unit.TypeIssues, unit.TypePullRequests) - reqRepoProjectsReader := context.RequireRepoReader(unit.TypeProjects) - reqRepoProjectsWriter := context.RequireRepoWriter(unit.TypeProjects) - reqRepoActionsReader := context.RequireRepoReader(unit.TypeActions) - reqRepoActionsWriter := context.RequireRepoWriter(unit.TypeActions) + reqRepoReleaseWriter := context.RequireUnitWriter(unit.TypeReleases) + reqRepoReleaseReader := context.RequireUnitReader(unit.TypeReleases) + reqRepoIssuesOrPullsWriter := context.RequireUnitWriter(unit.TypeIssues, unit.TypePullRequests) + reqRepoIssuesOrPullsReader := context.RequireUnitReader(unit.TypeIssues, unit.TypePullRequests) + reqRepoProjectsReader := context.RequireUnitReader(unit.TypeProjects) + reqRepoProjectsWriter := context.RequireUnitWriter(unit.TypeProjects) + reqRepoActionsReader := context.RequireUnitReader(unit.TypeActions) + reqRepoActionsWriter := context.RequireUnitWriter(unit.TypeActions) + + // the legacy names "reqRepoXxx" should be renamed to the correct name "reqUnitXxx", these permissions are for units, not repos + reqUnitsWithMarkdown := context.RequireUnitReader(unit.TypeCode, unit.TypeIssues, unit.TypePullRequests, unit.TypeReleases, unit.TypeWiki) + reqUnitCodeReader := context.RequireUnitReader(unit.TypeCode) + reqUnitIssuesReader := context.RequireUnitReader(unit.TypeIssues) + reqUnitPullsReader := context.RequireUnitReader(unit.TypePullRequests) + reqUnitWikiReader := context.RequireUnitReader(unit.TypeWiki) + reqUnitWikiWriter := context.RequireUnitWriter(unit.TypeWiki) reqPackageAccess := func(accessMode perm.AccessMode) func(ctx *context.Context) { return func(ctx *context.Context) { @@ -1053,7 +1056,7 @@ func registerRoutes(m *web.Router) { m.Group("/migrate", func() { m.Get("/status", repo.MigrateStatus) }) - }, optSignIn, context.RepoAssignment, reqRepoCodeReader) + }, optSignIn, context.RepoAssignment, reqUnitCodeReader) // end "/{username}/{reponame}/-": migrate m.Group("/{username}/{reponame}/settings", func() { @@ -1151,8 +1154,7 @@ func registerRoutes(m *web.Router) { // user/org home, including rss feeds m.Get("/{username}/{reponame}", optSignIn, context.RepoAssignment, context.RepoRef(), repo.SetEditorconfigIfExists, repo.Home) - // TODO: maybe it should relax the permission to allow "any access" - m.Post("/{username}/{reponame}/markup", optSignIn, context.RepoAssignment, context.RequireRepoReaderOr(unit.TypeCode, unit.TypeIssues, unit.TypePullRequests, unit.TypeReleases, unit.TypeWiki), web.Bind(structs.MarkupOption{}), misc.Markup) + m.Post("/{username}/{reponame}/markup", optSignIn, context.RepoAssignment, reqUnitsWithMarkdown, web.Bind(structs.MarkupOption{}), misc.Markup) m.Group("/{username}/{reponame}", func() { m.Get("/find/*", repo.FindFiles) @@ -1164,41 +1166,40 @@ func registerRoutes(m *web.Router) { m.Get("/compare", repo.MustBeNotEmpty, repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.CompareDiff) m.Combo("/compare/*", repo.MustBeNotEmpty, repo.SetEditorconfigIfExists). Get(repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.CompareDiff). - Post(reqSignIn, context.RepoMustNotBeArchived(), reqRepoPullsReader, repo.MustAllowPulls, web.Bind(forms.CreateIssueForm{}), repo.SetWhitespaceBehavior, repo.CompareAndPullRequestPost) - }, optSignIn, context.RepoAssignment, reqRepoCodeReader) + Post(reqSignIn, context.RepoMustNotBeArchived(), reqUnitPullsReader, repo.MustAllowPulls, web.Bind(forms.CreateIssueForm{}), repo.SetWhitespaceBehavior, repo.CompareAndPullRequestPost) + }, optSignIn, context.RepoAssignment, reqUnitCodeReader) // end "/{username}/{reponame}": repo code: find, compare, list + addIssuesPullsViewRoutes := func() { + // for /{username}/{reponame}/issues" or "/{username}/{reponame}/pulls" + m.Get("/posters", repo.IssuePullPosters) + m.Group("/{index}", func() { + m.Get("/info", repo.GetIssueInfo) + m.Get("/attachments", repo.GetIssueAttachments) + m.Get("/attachments/{uuid}", repo.GetAttachment) + m.Group("/content-history", func() { + m.Get("/overview", repo.GetContentHistoryOverview) + m.Get("/list", repo.GetContentHistoryList) + m.Get("/detail", repo.GetContentHistoryDetail) + }) + }) + } m.Group("/{username}/{reponame}", func() { - m.Get("/issues/posters", repo.IssuePosters) // it can't use {type:issues|pulls} because it would conflict with other routes like "/pulls/{index}" - m.Get("/pulls/posters", repo.PullPosters) m.Get("/comments/{id}/attachments", repo.GetCommentAttachments) m.Get("/labels", repo.RetrieveLabelsForList, repo.Labels) m.Get("/milestones", repo.Milestones) m.Get("/milestone/{id}", context.RepoRef(), repo.MilestoneIssuesAndPulls) - m.Group("/{type:issues|pulls}", func() { - m.Group("/{index}", func() { - m.Get("/info", repo.GetIssueInfo) - m.Get("/attachments", repo.GetIssueAttachments) - m.Get("/attachments/{uuid}", repo.GetAttachment) - m.Group("/content-history", func() { - m.Get("/overview", repo.GetContentHistoryOverview) - m.Get("/list", repo.GetContentHistoryList) - m.Get("/detail", repo.GetContentHistoryDetail) - }) - }) - }, context.RepoRef()) m.Get("/issues/suggestions", repo.IssueSuggestions) - }, optSignIn, context.RepoAssignment, reqRepoIssuesOrPullsReader) + }, optSignIn, context.RepoAssignment, reqRepoIssuesOrPullsReader) // issue/pull attachments, labels, milestones + + m.Group("/{username}/{reponame}/{type:issues}", addIssuesPullsViewRoutes, optSignIn, context.RepoAssignment, reqUnitIssuesReader) + m.Group("/{username}/{reponame}/{type:pulls}", addIssuesPullsViewRoutes, optSignIn, context.RepoAssignment, reqUnitPullsReader) // end "/{username}/{reponame}": view milestone, label, issue, pull, etc - m.Group("/{username}/{reponame}", func() { - m.Group("/{type:issues|pulls}", func() { - m.Get("", repo.Issues) - m.Group("/{index}", func() { - m.Get("", repo.ViewIssue) - }) - }) - }, optSignIn, context.RepoAssignment, context.RequireRepoReaderOr(unit.TypeIssues, unit.TypePullRequests, unit.TypeExternalTracker)) + m.Group("/{username}/{reponame}/{type:issues}", func() { + m.Get("", repo.Issues) + m.Get("/{index}", repo.ViewIssue) + }, optSignIn, context.RepoAssignment, context.RequireUnitReader(unit.TypeIssues, unit.TypeExternalTracker)) // end "/{username}/{reponame}": issue/pull list, issue/pull view, external tracker m.Group("/{username}/{reponame}", func() { // edit issues, pulls, labels, milestones, etc @@ -1209,11 +1210,10 @@ func registerRoutes(m *web.Router) { m.Get("/choose", context.RepoRef(), repo.NewIssueChooseTemplate) }) m.Get("/search", repo.SearchRepoIssuesJSON) - }, context.RepoMustNotBeArchived(), reqRepoIssueReader) + }, reqUnitIssuesReader) - // FIXME: should use different URLs but mostly same logic for comments of issue and pull request. - // So they can apply their own enable/disable logic on routers. - m.Group("/{type:issues|pulls}", func() { + addIssuesPullsRoutes := func() { + // for "/{username}/{reponame}/issues" or "/{username}/{reponame}/pulls" m.Group("/{index}", func() { m.Post("/title", repo.UpdateIssueTitle) m.Post("/content", repo.UpdateIssueContent) @@ -1240,39 +1240,37 @@ func registerRoutes(m *web.Router) { m.Post("/lock", reqRepoIssuesOrPullsWriter, web.Bind(forms.IssueLockForm{}), repo.LockIssue) m.Post("/unlock", reqRepoIssuesOrPullsWriter, repo.UnlockIssue) m.Post("/delete", reqRepoAdmin, repo.DeleteIssue) - }, context.RepoMustNotBeArchived()) - - m.Group("/{index}", func() { m.Post("/content-history/soft-delete", repo.SoftDeleteContentHistory) }) - m.Post("/labels", reqRepoIssuesOrPullsWriter, repo.UpdateIssueLabel) - m.Post("/milestone", reqRepoIssuesOrPullsWriter, repo.UpdateIssueMilestone) + m.Post("/attachments", repo.UploadIssueAttachment) + m.Post("/attachments/remove", repo.DeleteAttachment) + m.Post("/projects", reqRepoIssuesOrPullsWriter, reqRepoProjectsReader, repo.UpdateIssueProject) m.Post("/assignee", reqRepoIssuesOrPullsWriter, repo.UpdateIssueAssignee) - m.Post("/request_review", repo.UpdatePullReviewRequest) - m.Post("/dismiss_review", reqRepoAdmin, web.Bind(forms.DismissReviewForm{}), repo.DismissReview) m.Post("/status", reqRepoIssuesOrPullsWriter, repo.UpdateIssueStatus) m.Post("/delete", reqRepoAdmin, repo.BatchDeleteIssues) - m.Post("/resolve_conversation", repo.SetShowOutdatedComments, repo.UpdateResolveConversation) - m.Post("/attachments", repo.UploadIssueAttachment) - m.Post("/attachments/remove", repo.DeleteAttachment) m.Delete("/unpin/{index}", reqRepoAdmin, repo.IssueUnpin) m.Post("/move_pin", reqRepoAdmin, repo.IssuePinMove) - }, context.RepoMustNotBeArchived()) + } + m.Group("/{type:issues}", addIssuesPullsRoutes, reqUnitIssuesReader, context.RepoMustNotBeArchived()) + m.Group("/{type:pulls}", addIssuesPullsRoutes, reqUnitPullsReader, context.RepoMustNotBeArchived()) m.Group("/comments/{id}", func() { m.Post("", repo.UpdateCommentContent) m.Post("/delete", repo.DeleteComment) m.Post("/reactions/{action}", web.Bind(forms.ReactionForm{}), repo.ChangeCommentReaction) - }, context.RepoMustNotBeArchived()) + }, reqRepoIssuesOrPullsReader) // edit issue/pull comment + m.Post("/labels", reqRepoIssuesOrPullsWriter, repo.UpdateIssueLabel) m.Group("/labels", func() { m.Post("/new", web.Bind(forms.CreateLabelForm{}), repo.NewLabel) m.Post("/edit", web.Bind(forms.CreateLabelForm{}), repo.UpdateLabel) m.Post("/delete", repo.DeleteLabel) m.Post("/initialize", web.Bind(forms.InitializeLabelsForm{}), repo.InitializeLabels) - }, context.RepoMustNotBeArchived(), reqRepoIssuesOrPullsWriter, context.RepoRef()) + }, reqRepoIssuesOrPullsWriter, context.RepoRef()) + + m.Post("/milestone", reqRepoIssuesOrPullsWriter, repo.UpdateIssueMilestone) m.Group("/milestones", func() { m.Combo("/new").Get(repo.NewMilestone). Post(web.Bind(forms.CreateMilestoneForm{}), repo.NewMilestonePost) @@ -1280,11 +1278,15 @@ func registerRoutes(m *web.Router) { m.Post("/{id}/edit", web.Bind(forms.CreateMilestoneForm{}), repo.EditMilestonePost) m.Post("/{id}/{action}", repo.ChangeMilestoneStatus) m.Post("/delete", repo.DeleteMilestone) - }, context.RepoMustNotBeArchived(), reqRepoIssuesOrPullsWriter, context.RepoRef()) - m.Group("/pull", func() { - m.Post("/{index}/target_branch", repo.UpdatePullRequestTarget) - }, context.RepoMustNotBeArchived()) - }, reqSignIn, context.RepoAssignment, reqRepoIssuesOrPullsReader) + }, reqRepoIssuesOrPullsWriter, context.RepoRef()) + + m.Group("", func() { + m.Post("/request_review", repo.UpdatePullReviewRequest) + m.Post("/dismiss_review", reqRepoAdmin, web.Bind(forms.DismissReviewForm{}), repo.DismissReview) + m.Post("/resolve_conversation", repo.SetShowOutdatedComments, repo.UpdateResolveConversation) + m.Post("/pull/{index}/target_branch", repo.UpdatePullRequestTarget) + }, reqUnitPullsReader) + }, reqSignIn, context.RepoAssignment, context.RepoMustNotBeArchived()) // end "/{username}/{reponame}": create or edit issues, pulls, labels, milestones m.Group("/{username}/{reponame}", func() { // repo code @@ -1324,7 +1326,7 @@ func registerRoutes(m *web.Router) { }, context.RepoMustNotBeArchived(), reqRepoCodeWriter, repo.MustBeNotEmpty) m.Combo("/fork").Get(repo.Fork).Post(web.Bind(forms.CreateRepoForm{}), repo.ForkPost) - }, reqSignIn, context.RepoAssignment, reqRepoCodeReader) + }, reqSignIn, context.RepoAssignment, reqUnitCodeReader) // end "/{username}/{reponame}": repo code m.Group("/{username}/{reponame}", func() { // repo tags @@ -1337,7 +1339,7 @@ func registerRoutes(m *web.Router) { repo.MustBeNotEmpty, context.RepoRefByType(context.RepoRefTag, context.RepoRefByTypeOptions{IgnoreNotExistErr: true})) m.Post("/tags/delete", repo.DeleteTag, reqSignIn, repo.MustBeNotEmpty, context.RepoMustNotBeArchived(), reqRepoCodeWriter, context.RepoRef()) - }, optSignIn, context.RepoAssignment, reqRepoCodeReader) + }, optSignIn, context.RepoAssignment, reqUnitCodeReader) // end "/{username}/{reponame}": repo tags m.Group("/{username}/{reponame}", func() { // repo releases @@ -1440,43 +1442,48 @@ func registerRoutes(m *web.Router) { m.Group("/{username}/{reponame}/wiki", func() { m.Combo(""). Get(repo.Wiki). - Post(context.RepoMustNotBeArchived(), reqSignIn, reqRepoWikiWriter, web.Bind(forms.NewWikiForm{}), repo.WikiPost) + Post(context.RepoMustNotBeArchived(), reqSignIn, reqUnitWikiWriter, web.Bind(forms.NewWikiForm{}), repo.WikiPost) m.Combo("/*"). Get(repo.Wiki). - Post(context.RepoMustNotBeArchived(), reqSignIn, reqRepoWikiWriter, web.Bind(forms.NewWikiForm{}), repo.WikiPost) + Post(context.RepoMustNotBeArchived(), reqSignIn, reqUnitWikiWriter, web.Bind(forms.NewWikiForm{}), repo.WikiPost) m.Get("/blob_excerpt/{sha}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.ExcerptBlob) m.Get("/commit/{sha:[a-f0-9]{7,64}}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.Diff) m.Get("/commit/{sha:[a-f0-9]{7,64}}.{ext:patch|diff}", repo.RawDiff) m.Get("/raw/*", repo.WikiRaw) - }, optSignIn, context.RepoAssignment, repo.MustEnableWiki, reqRepoWikiReader, func(ctx *context.Context) { + }, optSignIn, context.RepoAssignment, repo.MustEnableWiki, reqUnitWikiReader, func(ctx *context.Context) { ctx.Data["PageIsWiki"] = true ctx.Data["CloneButtonOriginLink"] = ctx.Repo.Repository.WikiCloneLink(ctx, ctx.Doer) }) // end "/{username}/{reponame}/wiki" m.Group("/{username}/{reponame}/activity", func() { + // activity has its own permission checks m.Get("", repo.Activity) m.Get("/{period}", repo.Activity) - m.Group("/contributors", func() { - m.Get("", repo.Contributors) - m.Get("/data", repo.ContributorsData) - }) - m.Group("/code-frequency", func() { - m.Get("", repo.CodeFrequency) - m.Get("/data", repo.CodeFrequencyData) - }) - m.Group("/recent-commits", func() { - m.Get("", repo.RecentCommits) - m.Get("/data", repo.RecentCommitsData) - }) + + m.Group("", func() { + m.Group("/contributors", func() { + m.Get("", repo.Contributors) + m.Get("/data", repo.ContributorsData) + }) + m.Group("/code-frequency", func() { + m.Get("", repo.CodeFrequency) + m.Get("/data", repo.CodeFrequencyData) + }) + m.Group("/recent-commits", func() { + m.Get("", repo.RecentCommits) + m.Get("/data", repo.RecentCommitsData) + }) + }, reqUnitCodeReader) }, - optSignIn, context.RepoAssignment, context.RequireRepoReaderOr(unit.TypePullRequests, unit.TypeIssues, unit.TypeReleases), - context.RepoRef(), repo.MustBeNotEmpty, + optSignIn, context.RepoAssignment, repo.MustBeNotEmpty, + context.RequireUnitReader(unit.TypeCode, unit.TypeIssues, unit.TypePullRequests, unit.TypeReleases), ) // end "/{username}/{reponame}/activity" m.Group("/{username}/{reponame}", func() { - m.Group("/pulls/{index}", func() { + m.Get("/{type:pulls}", repo.Issues) + m.Group("/{type:pulls}/{index}", func() { m.Get("", repo.SetWhitespaceBehavior, repo.GetPullDiffStats, repo.ViewIssue) m.Get(".diff", repo.DownloadPullDiff) m.Get(".patch", repo.DownloadPullPatch) @@ -1501,7 +1508,7 @@ func registerRoutes(m *web.Router) { }, context.RepoMustNotBeArchived()) }) }) - }, optSignIn, context.RepoAssignment, repo.MustAllowPulls, reqRepoPullsReader) + }, optSignIn, context.RepoAssignment, repo.MustAllowPulls, reqUnitPullsReader) // end "/{username}/{reponame}/pulls/{index}": repo pull request m.Group("/{username}/{reponame}", func() { @@ -1582,13 +1589,13 @@ func registerRoutes(m *web.Router) { m.Get("/forks", context.RepoRef(), repo.Forks) m.Get("/commit/{sha:([a-f0-9]{7,64})}.{ext:patch|diff}", repo.MustBeNotEmpty, repo.RawDiff) m.Post("/lastcommit/*", context.RepoRefByType(context.RepoRefCommit), repo.LastCommit) - }, optSignIn, context.RepoAssignment, reqRepoCodeReader) + }, optSignIn, context.RepoAssignment, reqUnitCodeReader) // end "/{username}/{reponame}": repo code m.Group("/{username}/{reponame}", func() { m.Get("/stars", repo.Stars) m.Get("/watchers", repo.Watchers) - m.Get("/search", reqRepoCodeReader, repo.Search) + m.Get("/search", reqUnitCodeReader, repo.Search) m.Post("/action/{action}", reqSignIn, repo.Action) }, optSignIn, context.RepoAssignment, context.RepoRef()) diff --git a/services/context/permission.go b/services/context/permission.go index 9338587257c..359d51c2726 100644 --- a/services/context/permission.go +++ b/services/context/permission.go @@ -9,24 +9,13 @@ import ( auth_model "code.gitea.io/gitea/models/auth" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" - "code.gitea.io/gitea/modules/log" ) // RequireRepoAdmin returns a middleware for requiring repository admin permission func RequireRepoAdmin() func(ctx *Context) { return func(ctx *Context) { if !ctx.IsSigned || !ctx.Repo.IsAdmin() { - ctx.NotFound(ctx.Req.URL.RequestURI(), nil) - return - } - } -} - -// RequireRepoWriter returns a middleware for requiring repository write to the specify unitType -func RequireRepoWriter(unitType unit.Type) func(ctx *Context) { - return func(ctx *Context) { - if !ctx.Repo.CanWrite(unitType) { - ctx.NotFound(ctx.Req.URL.RequestURI(), nil) + ctx.NotFound("RequireRepoAdmin denies the request", nil) return } } @@ -42,75 +31,30 @@ func CanEnableEditor() func(ctx *Context) { } } -// RequireRepoWriterOr returns a middleware for requiring repository write to one of the unit permission -func RequireRepoWriterOr(unitTypes ...unit.Type) func(ctx *Context) { +// RequireUnitWriter returns a middleware for requiring repository write to one of the unit permission +func RequireUnitWriter(unitTypes ...unit.Type) func(ctx *Context) { return func(ctx *Context) { for _, unitType := range unitTypes { if ctx.Repo.CanWrite(unitType) { return } } - ctx.NotFound(ctx.Req.URL.RequestURI(), nil) + ctx.NotFound("RequireUnitWriter denies the request", nil) } } -// RequireRepoReader returns a middleware for requiring repository read to the specify unitType -func RequireRepoReader(unitType unit.Type) func(ctx *Context) { - return func(ctx *Context) { - if !ctx.Repo.CanRead(unitType) { - if unitType == unit.TypeCode && canWriteAsMaintainer(ctx) { - return - } - if log.IsTrace() { - if ctx.IsSigned { - log.Trace("Permission Denied: User %-v cannot read %-v in Repo %-v\n"+ - "User in Repo has Permissions: %-+v", - ctx.Doer, - unitType, - ctx.Repo.Repository, - ctx.Repo.Permission) - } else { - log.Trace("Permission Denied: Anonymous user cannot read %-v in Repo %-v\n"+ - "Anonymous user in Repo has Permissions: %-+v", - unitType, - ctx.Repo.Repository, - ctx.Repo.Permission) - } - } - ctx.NotFound(ctx.Req.URL.RequestURI(), nil) - return - } - } -} - -// RequireRepoReaderOr returns a middleware for requiring repository write to one of the unit permission -func RequireRepoReaderOr(unitTypes ...unit.Type) func(ctx *Context) { +// RequireUnitReader returns a middleware for requiring repository write to one of the unit permission +func RequireUnitReader(unitTypes ...unit.Type) func(ctx *Context) { return func(ctx *Context) { for _, unitType := range unitTypes { if ctx.Repo.CanRead(unitType) { return } - } - if log.IsTrace() { - var format string - var args []any - if ctx.IsSigned { - format = "Permission Denied: User %-v cannot read [" - args = append(args, ctx.Doer) - } else { - format = "Permission Denied: Anonymous user cannot read [" - } - for _, unit := range unitTypes { - format += "%-v, " - args = append(args, unit) + if unitType == unit.TypeCode && canWriteAsMaintainer(ctx) { + return } - - format = format[:len(format)-2] + "] in Repo %-v\n" + - "User in Repo has Permissions: %-+v" - args = append(args, ctx.Repo.Repository, ctx.Repo.Permission) - log.Trace(format, args...) } - ctx.NotFound(ctx.Req.URL.RequestURI(), nil) + ctx.NotFound("RequireUnitReader denies the request", nil) } } diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index 35ea5378d3a..4f9806dc937 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -110,41 +110,51 @@ type RepoSettingForm struct { EnablePrune bool // Advanced settings - EnableCode bool - EnableWiki bool - EnableExternalWiki bool - DefaultWikiBranch string - DefaultWikiEveryoneAccess string - ExternalWikiURL string + EnableCode bool + DefaultCodeEveryoneAccess string + + EnableWiki bool + EnableExternalWiki bool + DefaultWikiBranch string + DefaultWikiEveryoneAccess string + ExternalWikiURL string + EnableIssues bool + DefaultIssuesEveryoneAccess string EnableExternalTracker bool ExternalTrackerURL string TrackerURLFormat string TrackerIssueStyle string ExternalTrackerRegexpPattern string EnableCloseIssuesViaCommitInAnyBranch bool - EnableProjects bool - ProjectsMode string - 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 - IsArchived bool + + EnableProjects bool + ProjectsMode string + + EnableReleases bool + + EnablePackages bool + + EnablePulls 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 + + EnableActions bool + + IsArchived bool // Signing Settings TrustModel string diff --git a/templates/repo/settings/options.tmpl b/templates/repo/settings/options.tmpl index 21eaf6a6518..cb596f013b7 100644 --- a/templates/repo/settings/options.tmpl +++ b/templates/repo/settings/options.tmpl @@ -302,6 +302,15 @@ +
+ {{$unitCode := .Repository.MustGetUnit ctx ctx.Consts.RepoUnitTypeCode}} + + +
{{$isInternalWikiEnabled := .Repository.UnitEnabled ctx ctx.Consts.RepoUnitTypeWiki}} @@ -331,7 +340,7 @@
{{$unitInternalWiki := .Repository.MustGetUnit ctx ctx.Consts.RepoUnitTypeWiki}} - + + {{/* everyone access mode is different from others, none means it is unset and won't be applied */}} + + + +
{{if .Repository.CanEnableTimetracker}}