Support public code/issue access for private repositories (#33127)

Close #8649, close #639 (will add "anonymous access" in following PRs)
pull/33255/head^2
wxiaoguang 2 weeks ago committed by GitHub
parent ecd463c2f1
commit a98a836e76
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 47
      models/perm/access/repo_permission.go
  2. 12
      models/perm/access/repo_permission_test.go
  3. 2
      options/locale/locale_en-US.ini
  4. 10
      routers/web/repo/issue_poster.go
  5. 17
      routers/web/repo/setting/setting.go
  6. 183
      routers/web/web.go
  7. 74
      services/context/permission.go
  8. 66
      services/forms/repo_form.go
  9. 20
      templates/repo/settings/options.tmpl

@ -175,10 +175,14 @@ func (p *Permission) LogString() string {
return fmt.Sprintf(format, args...) 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 { 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 return
} }
// apply everyone access permissions
for _, u := range perm.units { for _, u := range perm.units {
if u.EveryoneAccessMode >= perm_model.AccessModeRead && u.EveryoneAccessMode > perm.everyoneAccessMode[u.Type] { if u.EveryoneAccessMode >= perm_model.AccessModeRead && u.EveryoneAccessMode > perm.everyoneAccessMode[u.Type] {
if perm.everyoneAccessMode == nil { if perm.everyoneAccessMode == nil {
@ -187,17 +191,40 @@ func applyEveryoneRepoPermission(user *user_model.User, perm *Permission) {
perm.everyoneAccessMode[u.Type] = u.EveryoneAccessMode 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 // 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) { func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (perm Permission, err error) {
defer func() { defer func() {
if err == nil { if err == nil {
applyEveryoneRepoPermission(user, &perm) finalProcessRepoUnitPermission(user, &perm)
}
if log.IsTrace() {
log.Trace("Permission Loaded for user %-v in repo %-v, permissions: %-+v", user, repo, perm)
} }
log.Trace("Permission Loaded for user %-v in repo %-v, permissions: %-+v", user, repo, perm)
}() }()
if err = repo.LoadUnits(ctx); err != nil { 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 return perm, err
} }

@ -50,7 +50,7 @@ func TestApplyEveryoneRepoPermission(t *testing.T) {
{Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead}, {Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead},
}, },
} }
applyEveryoneRepoPermission(nil, &perm) finalProcessRepoUnitPermission(nil, &perm)
assert.False(t, perm.CanRead(unit.TypeWiki)) assert.False(t, perm.CanRead(unit.TypeWiki))
perm = Permission{ perm = Permission{
@ -59,7 +59,7 @@ func TestApplyEveryoneRepoPermission(t *testing.T) {
{Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead}, {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)) assert.False(t, perm.CanRead(unit.TypeWiki))
perm = Permission{ perm = Permission{
@ -68,7 +68,7 @@ func TestApplyEveryoneRepoPermission(t *testing.T) {
{Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead}, {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)) assert.True(t, perm.CanRead(unit.TypeWiki))
perm = Permission{ perm = Permission{
@ -77,20 +77,22 @@ func TestApplyEveryoneRepoPermission(t *testing.T) {
{Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead}, {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 // it should work the same as "EveryoneAccessMode: none" because the default AccessMode should be applied to units
assert.True(t, perm.CanWrite(unit.TypeWiki)) assert.True(t, perm.CanWrite(unit.TypeWiki))
perm = Permission{ perm = Permission{
units: []*repo_model.RepoUnit{ units: []*repo_model.RepoUnit{
{Type: unit.TypeCode}, // will be removed
{Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead}, {Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead},
}, },
unitsMode: map[unit.Type]perm_model.AccessMode{ unitsMode: map[unit.Type]perm_model.AccessMode{
unit.TypeWiki: perm_model.AccessModeWrite, 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.True(t, perm.CanWrite(unit.TypeWiki))
assert.Len(t, perm.units, 1)
} }
func TestUnitAccessMode(t *testing.T) { func TestUnitAccessMode(t *testing.T) {

@ -2158,7 +2158,7 @@ settings.advanced_settings = Advanced Settings
settings.wiki_desc = Enable Repository Wiki settings.wiki_desc = Enable Repository Wiki
settings.use_internal_wiki = Use Built-In Wiki settings.use_internal_wiki = Use Built-In Wiki
settings.default_wiki_branch_name = Default Wiki Branch Name 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.failed_to_change_default_wiki_branch = Failed to change the default wiki branch.
settings.use_external_wiki = Use External Wiki settings.use_external_wiki = Use External Wiki
settings.external_wiki_url = External Wiki URL settings.external_wiki_url = External Wiki URL

@ -26,13 +26,9 @@ type userSearchResponse struct {
Results []*userSearchInfo `json:"results"` Results []*userSearchInfo `json:"results"`
} }
// IssuePosters get posters for current repo's issues/pull requests func IssuePullPosters(ctx *context.Context) {
func IssuePosters(ctx *context.Context) { isPullList := ctx.PathParam("type") == "pulls"
issuePosters(ctx, false) issuePosters(ctx, isPullList)
}
func PullPosters(ctx *context.Context) {
issuePosters(ctx, true)
} }
func issuePosters(ctx *context.Context, isPullList bool) { func issuePosters(ctx *context.Context, isPullList bool) {

@ -49,6 +49,15 @@ const (
tplDeployKeys templates.TplName = "repo/settings/deploy_keys" 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 // SettingsCtxData is a middleware that sets all the general context data for the
// settings template. // settings template.
func SettingsCtxData(ctx *context.Context) { func SettingsCtxData(ctx *context.Context) {
@ -447,8 +456,9 @@ func SettingsPost(ctx *context.Context) {
if form.EnableCode && !unit_model.TypeCode.UnitGlobalDisabled() { if form.EnableCode && !unit_model.TypeCode.UnitGlobalDisabled() {
units = append(units, repo_model.RepoUnit{ units = append(units, repo_model.RepoUnit{
RepoID: repo.ID, RepoID: repo.ID,
Type: unit_model.TypeCode, Type: unit_model.TypeCode,
EveryoneAccessMode: parseEveryoneAccessMode(form.DefaultCodeEveryoneAccess, perm.AccessModeNone, perm.AccessModeRead),
}) })
} else if !unit_model.TypeCode.UnitGlobalDisabled() { } else if !unit_model.TypeCode.UnitGlobalDisabled() {
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeCode) deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeCode)
@ -474,7 +484,7 @@ func SettingsPost(ctx *context.Context) {
RepoID: repo.ID, RepoID: repo.ID,
Type: unit_model.TypeWiki, Type: unit_model.TypeWiki,
Config: new(repo_model.UnitConfig), 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) deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalWiki)
} else { } else {
@ -524,6 +534,7 @@ func SettingsPost(ctx *context.Context) {
AllowOnlyContributorsToTrackTime: form.AllowOnlyContributorsToTrackTime, AllowOnlyContributorsToTrackTime: form.AllowOnlyContributorsToTrackTime,
EnableDependencies: form.EnableIssueDependencies, EnableDependencies: form.EnableIssueDependencies,
}, },
EveryoneAccessMode: parseEveryoneAccessMode(form.DefaultIssuesEveryoneAccess, perm.AccessModeNone, perm.AccessModeRead),
}) })
deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalTracker) deleteUnitTypes = append(deleteUnitTypes, unit_model.TypeExternalTracker)
} else { } else {

@ -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 group.Add(&auth_service.Basic{}) // FIXME: this should be removed and only applied in download and git/lfs routers
if setting.Service.EnableReverseProxyAuth { 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{}) group.Add(&auth_service.Session{})
@ -816,21 +816,24 @@ func registerRoutes(m *web.Router) {
m.Post("/{username}", reqSignIn, context.UserAssignmentWeb(), user.Action) m.Post("/{username}", reqSignIn, context.UserAssignmentWeb(), user.Action)
reqRepoAdmin := context.RequireRepoAdmin() reqRepoAdmin := context.RequireRepoAdmin()
reqRepoCodeWriter := context.RequireRepoWriter(unit.TypeCode) reqRepoCodeWriter := context.RequireUnitWriter(unit.TypeCode)
canEnableEditor := context.CanEnableEditor() canEnableEditor := context.CanEnableEditor()
reqRepoCodeReader := context.RequireRepoReader(unit.TypeCode) reqRepoReleaseWriter := context.RequireUnitWriter(unit.TypeReleases)
reqRepoReleaseWriter := context.RequireRepoWriter(unit.TypeReleases) reqRepoReleaseReader := context.RequireUnitReader(unit.TypeReleases)
reqRepoReleaseReader := context.RequireRepoReader(unit.TypeReleases) reqRepoIssuesOrPullsWriter := context.RequireUnitWriter(unit.TypeIssues, unit.TypePullRequests)
reqRepoWikiReader := context.RequireRepoReader(unit.TypeWiki) reqRepoIssuesOrPullsReader := context.RequireUnitReader(unit.TypeIssues, unit.TypePullRequests)
reqRepoWikiWriter := context.RequireRepoWriter(unit.TypeWiki) reqRepoProjectsReader := context.RequireUnitReader(unit.TypeProjects)
reqRepoIssueReader := context.RequireRepoReader(unit.TypeIssues) reqRepoProjectsWriter := context.RequireUnitWriter(unit.TypeProjects)
reqRepoPullsReader := context.RequireRepoReader(unit.TypePullRequests) reqRepoActionsReader := context.RequireUnitReader(unit.TypeActions)
reqRepoIssuesOrPullsWriter := context.RequireRepoWriterOr(unit.TypeIssues, unit.TypePullRequests) reqRepoActionsWriter := context.RequireUnitWriter(unit.TypeActions)
reqRepoIssuesOrPullsReader := context.RequireRepoReaderOr(unit.TypeIssues, unit.TypePullRequests)
reqRepoProjectsReader := context.RequireRepoReader(unit.TypeProjects) // the legacy names "reqRepoXxx" should be renamed to the correct name "reqUnitXxx", these permissions are for units, not repos
reqRepoProjectsWriter := context.RequireRepoWriter(unit.TypeProjects) reqUnitsWithMarkdown := context.RequireUnitReader(unit.TypeCode, unit.TypeIssues, unit.TypePullRequests, unit.TypeReleases, unit.TypeWiki)
reqRepoActionsReader := context.RequireRepoReader(unit.TypeActions) reqUnitCodeReader := context.RequireUnitReader(unit.TypeCode)
reqRepoActionsWriter := context.RequireRepoWriter(unit.TypeActions) 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) { reqPackageAccess := func(accessMode perm.AccessMode) func(ctx *context.Context) {
return func(ctx *context.Context) { return func(ctx *context.Context) {
@ -1053,7 +1056,7 @@ func registerRoutes(m *web.Router) {
m.Group("/migrate", func() { m.Group("/migrate", func() {
m.Get("/status", repo.MigrateStatus) m.Get("/status", repo.MigrateStatus)
}) })
}, optSignIn, context.RepoAssignment, reqRepoCodeReader) }, optSignIn, context.RepoAssignment, reqUnitCodeReader)
// end "/{username}/{reponame}/-": migrate // end "/{username}/{reponame}/-": migrate
m.Group("/{username}/{reponame}/settings", func() { m.Group("/{username}/{reponame}/settings", func() {
@ -1151,8 +1154,7 @@ func registerRoutes(m *web.Router) {
// user/org home, including rss feeds // user/org home, including rss feeds
m.Get("/{username}/{reponame}", optSignIn, context.RepoAssignment, context.RepoRef(), repo.SetEditorconfigIfExists, repo.Home) 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, reqUnitsWithMarkdown, web.Bind(structs.MarkupOption{}), misc.Markup)
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.Group("/{username}/{reponame}", func() { m.Group("/{username}/{reponame}", func() {
m.Get("/find/*", repo.FindFiles) 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.Get("/compare", repo.MustBeNotEmpty, repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.CompareDiff)
m.Combo("/compare/*", repo.MustBeNotEmpty, repo.SetEditorconfigIfExists). m.Combo("/compare/*", repo.MustBeNotEmpty, repo.SetEditorconfigIfExists).
Get(repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.CompareDiff). Get(repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.CompareDiff).
Post(reqSignIn, context.RepoMustNotBeArchived(), reqRepoPullsReader, repo.MustAllowPulls, web.Bind(forms.CreateIssueForm{}), repo.SetWhitespaceBehavior, repo.CompareAndPullRequestPost) Post(reqSignIn, context.RepoMustNotBeArchived(), reqUnitPullsReader, repo.MustAllowPulls, web.Bind(forms.CreateIssueForm{}), repo.SetWhitespaceBehavior, repo.CompareAndPullRequestPost)
}, optSignIn, context.RepoAssignment, reqRepoCodeReader) }, optSignIn, context.RepoAssignment, reqUnitCodeReader)
// end "/{username}/{reponame}": repo code: find, compare, list // 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.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("/comments/{id}/attachments", repo.GetCommentAttachments)
m.Get("/labels", repo.RetrieveLabelsForList, repo.Labels) m.Get("/labels", repo.RetrieveLabelsForList, repo.Labels)
m.Get("/milestones", repo.Milestones) m.Get("/milestones", repo.Milestones)
m.Get("/milestone/{id}", context.RepoRef(), repo.MilestoneIssuesAndPulls) 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) 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 // end "/{username}/{reponame}": view milestone, label, issue, pull, etc
m.Group("/{username}/{reponame}", func() { m.Group("/{username}/{reponame}/{type:issues}", func() {
m.Group("/{type:issues|pulls}", func() { m.Get("", repo.Issues)
m.Get("", repo.Issues) m.Get("/{index}", repo.ViewIssue)
m.Group("/{index}", func() { }, optSignIn, context.RepoAssignment, context.RequireUnitReader(unit.TypeIssues, unit.TypeExternalTracker))
m.Get("", repo.ViewIssue)
})
})
}, optSignIn, context.RepoAssignment, context.RequireRepoReaderOr(unit.TypeIssues, unit.TypePullRequests, unit.TypeExternalTracker))
// end "/{username}/{reponame}": issue/pull list, issue/pull view, external tracker // end "/{username}/{reponame}": issue/pull list, issue/pull view, external tracker
m.Group("/{username}/{reponame}", func() { // edit issues, pulls, labels, milestones, etc 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("/choose", context.RepoRef(), repo.NewIssueChooseTemplate)
}) })
m.Get("/search", repo.SearchRepoIssuesJSON) 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. addIssuesPullsRoutes := func() {
// So they can apply their own enable/disable logic on routers. // for "/{username}/{reponame}/issues" or "/{username}/{reponame}/pulls"
m.Group("/{type:issues|pulls}", func() {
m.Group("/{index}", func() { m.Group("/{index}", func() {
m.Post("/title", repo.UpdateIssueTitle) m.Post("/title", repo.UpdateIssueTitle)
m.Post("/content", repo.UpdateIssueContent) 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("/lock", reqRepoIssuesOrPullsWriter, web.Bind(forms.IssueLockForm{}), repo.LockIssue)
m.Post("/unlock", reqRepoIssuesOrPullsWriter, repo.UnlockIssue) m.Post("/unlock", reqRepoIssuesOrPullsWriter, repo.UnlockIssue)
m.Post("/delete", reqRepoAdmin, repo.DeleteIssue) m.Post("/delete", reqRepoAdmin, repo.DeleteIssue)
}, context.RepoMustNotBeArchived())
m.Group("/{index}", func() {
m.Post("/content-history/soft-delete", repo.SoftDeleteContentHistory) m.Post("/content-history/soft-delete", repo.SoftDeleteContentHistory)
}) })
m.Post("/labels", reqRepoIssuesOrPullsWriter, repo.UpdateIssueLabel) m.Post("/attachments", repo.UploadIssueAttachment)
m.Post("/milestone", reqRepoIssuesOrPullsWriter, repo.UpdateIssueMilestone) m.Post("/attachments/remove", repo.DeleteAttachment)
m.Post("/projects", reqRepoIssuesOrPullsWriter, reqRepoProjectsReader, repo.UpdateIssueProject) m.Post("/projects", reqRepoIssuesOrPullsWriter, reqRepoProjectsReader, repo.UpdateIssueProject)
m.Post("/assignee", reqRepoIssuesOrPullsWriter, repo.UpdateIssueAssignee) 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("/status", reqRepoIssuesOrPullsWriter, repo.UpdateIssueStatus)
m.Post("/delete", reqRepoAdmin, repo.BatchDeleteIssues) 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.Delete("/unpin/{index}", reqRepoAdmin, repo.IssueUnpin)
m.Post("/move_pin", reqRepoAdmin, repo.IssuePinMove) 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.Group("/comments/{id}", func() {
m.Post("", repo.UpdateCommentContent) m.Post("", repo.UpdateCommentContent)
m.Post("/delete", repo.DeleteComment) m.Post("/delete", repo.DeleteComment)
m.Post("/reactions/{action}", web.Bind(forms.ReactionForm{}), repo.ChangeCommentReaction) 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.Group("/labels", func() {
m.Post("/new", web.Bind(forms.CreateLabelForm{}), repo.NewLabel) m.Post("/new", web.Bind(forms.CreateLabelForm{}), repo.NewLabel)
m.Post("/edit", web.Bind(forms.CreateLabelForm{}), repo.UpdateLabel) m.Post("/edit", web.Bind(forms.CreateLabelForm{}), repo.UpdateLabel)
m.Post("/delete", repo.DeleteLabel) m.Post("/delete", repo.DeleteLabel)
m.Post("/initialize", web.Bind(forms.InitializeLabelsForm{}), repo.InitializeLabels) 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.Group("/milestones", func() {
m.Combo("/new").Get(repo.NewMilestone). m.Combo("/new").Get(repo.NewMilestone).
Post(web.Bind(forms.CreateMilestoneForm{}), repo.NewMilestonePost) 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}/edit", web.Bind(forms.CreateMilestoneForm{}), repo.EditMilestonePost)
m.Post("/{id}/{action}", repo.ChangeMilestoneStatus) m.Post("/{id}/{action}", repo.ChangeMilestoneStatus)
m.Post("/delete", repo.DeleteMilestone) m.Post("/delete", repo.DeleteMilestone)
}, context.RepoMustNotBeArchived(), reqRepoIssuesOrPullsWriter, context.RepoRef()) }, reqRepoIssuesOrPullsWriter, context.RepoRef())
m.Group("/pull", func() {
m.Post("/{index}/target_branch", repo.UpdatePullRequestTarget) m.Group("", func() {
}, context.RepoMustNotBeArchived()) m.Post("/request_review", repo.UpdatePullReviewRequest)
}, reqSignIn, context.RepoAssignment, reqRepoIssuesOrPullsReader) 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 // end "/{username}/{reponame}": create or edit issues, pulls, labels, milestones
m.Group("/{username}/{reponame}", func() { // repo code m.Group("/{username}/{reponame}", func() { // repo code
@ -1324,7 +1326,7 @@ func registerRoutes(m *web.Router) {
}, context.RepoMustNotBeArchived(), reqRepoCodeWriter, repo.MustBeNotEmpty) }, context.RepoMustNotBeArchived(), reqRepoCodeWriter, repo.MustBeNotEmpty)
m.Combo("/fork").Get(repo.Fork).Post(web.Bind(forms.CreateRepoForm{}), repo.ForkPost) 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 // end "/{username}/{reponame}": repo code
m.Group("/{username}/{reponame}", func() { // repo tags 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})) repo.MustBeNotEmpty, context.RepoRefByType(context.RepoRefTag, context.RepoRefByTypeOptions{IgnoreNotExistErr: true}))
m.Post("/tags/delete", repo.DeleteTag, reqSignIn, m.Post("/tags/delete", repo.DeleteTag, reqSignIn,
repo.MustBeNotEmpty, context.RepoMustNotBeArchived(), reqRepoCodeWriter, context.RepoRef()) repo.MustBeNotEmpty, context.RepoMustNotBeArchived(), reqRepoCodeWriter, context.RepoRef())
}, optSignIn, context.RepoAssignment, reqRepoCodeReader) }, optSignIn, context.RepoAssignment, reqUnitCodeReader)
// end "/{username}/{reponame}": repo tags // end "/{username}/{reponame}": repo tags
m.Group("/{username}/{reponame}", func() { // repo releases m.Group("/{username}/{reponame}", func() { // repo releases
@ -1440,43 +1442,48 @@ func registerRoutes(m *web.Router) {
m.Group("/{username}/{reponame}/wiki", func() { m.Group("/{username}/{reponame}/wiki", func() {
m.Combo(""). m.Combo("").
Get(repo.Wiki). 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("/*"). m.Combo("/*").
Get(repo.Wiki). 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("/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}}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.Diff)
m.Get("/commit/{sha:[a-f0-9]{7,64}}.{ext:patch|diff}", repo.RawDiff) m.Get("/commit/{sha:[a-f0-9]{7,64}}.{ext:patch|diff}", repo.RawDiff)
m.Get("/raw/*", repo.WikiRaw) 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["PageIsWiki"] = true
ctx.Data["CloneButtonOriginLink"] = ctx.Repo.Repository.WikiCloneLink(ctx, ctx.Doer) ctx.Data["CloneButtonOriginLink"] = ctx.Repo.Repository.WikiCloneLink(ctx, ctx.Doer)
}) })
// end "/{username}/{reponame}/wiki" // end "/{username}/{reponame}/wiki"
m.Group("/{username}/{reponame}/activity", func() { m.Group("/{username}/{reponame}/activity", func() {
// activity has its own permission checks
m.Get("", repo.Activity) m.Get("", repo.Activity)
m.Get("/{period}", repo.Activity) m.Get("/{period}", repo.Activity)
m.Group("/contributors", func() {
m.Get("", repo.Contributors) m.Group("", func() {
m.Get("/data", repo.ContributorsData) m.Group("/contributors", func() {
}) m.Get("", repo.Contributors)
m.Group("/code-frequency", func() { m.Get("/data", repo.ContributorsData)
m.Get("", repo.CodeFrequency) })
m.Get("/data", repo.CodeFrequencyData) m.Group("/code-frequency", func() {
}) m.Get("", repo.CodeFrequency)
m.Group("/recent-commits", func() { m.Get("/data", repo.CodeFrequencyData)
m.Get("", repo.RecentCommits) })
m.Get("/data", repo.RecentCommitsData) 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), optSignIn, context.RepoAssignment, repo.MustBeNotEmpty,
context.RepoRef(), repo.MustBeNotEmpty, context.RequireUnitReader(unit.TypeCode, unit.TypeIssues, unit.TypePullRequests, unit.TypeReleases),
) )
// end "/{username}/{reponame}/activity" // end "/{username}/{reponame}/activity"
m.Group("/{username}/{reponame}", func() { 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("", repo.SetWhitespaceBehavior, repo.GetPullDiffStats, repo.ViewIssue)
m.Get(".diff", repo.DownloadPullDiff) m.Get(".diff", repo.DownloadPullDiff)
m.Get(".patch", repo.DownloadPullPatch) m.Get(".patch", repo.DownloadPullPatch)
@ -1501,7 +1508,7 @@ func registerRoutes(m *web.Router) {
}, context.RepoMustNotBeArchived()) }, context.RepoMustNotBeArchived())
}) })
}) })
}, optSignIn, context.RepoAssignment, repo.MustAllowPulls, reqRepoPullsReader) }, optSignIn, context.RepoAssignment, repo.MustAllowPulls, reqUnitPullsReader)
// end "/{username}/{reponame}/pulls/{index}": repo pull request // end "/{username}/{reponame}/pulls/{index}": repo pull request
m.Group("/{username}/{reponame}", func() { m.Group("/{username}/{reponame}", func() {
@ -1582,13 +1589,13 @@ func registerRoutes(m *web.Router) {
m.Get("/forks", context.RepoRef(), repo.Forks) m.Get("/forks", context.RepoRef(), repo.Forks)
m.Get("/commit/{sha:([a-f0-9]{7,64})}.{ext:patch|diff}", repo.MustBeNotEmpty, repo.RawDiff) 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) m.Post("/lastcommit/*", context.RepoRefByType(context.RepoRefCommit), repo.LastCommit)
}, optSignIn, context.RepoAssignment, reqRepoCodeReader) }, optSignIn, context.RepoAssignment, reqUnitCodeReader)
// end "/{username}/{reponame}": repo code // end "/{username}/{reponame}": repo code
m.Group("/{username}/{reponame}", func() { m.Group("/{username}/{reponame}", func() {
m.Get("/stars", repo.Stars) m.Get("/stars", repo.Stars)
m.Get("/watchers", repo.Watchers) m.Get("/watchers", repo.Watchers)
m.Get("/search", reqRepoCodeReader, repo.Search) m.Get("/search", reqUnitCodeReader, repo.Search)
m.Post("/action/{action}", reqSignIn, repo.Action) m.Post("/action/{action}", reqSignIn, repo.Action)
}, optSignIn, context.RepoAssignment, context.RepoRef()) }, optSignIn, context.RepoAssignment, context.RepoRef())

@ -9,24 +9,13 @@ import (
auth_model "code.gitea.io/gitea/models/auth" auth_model "code.gitea.io/gitea/models/auth"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/log"
) )
// RequireRepoAdmin returns a middleware for requiring repository admin permission // RequireRepoAdmin returns a middleware for requiring repository admin permission
func RequireRepoAdmin() func(ctx *Context) { func RequireRepoAdmin() func(ctx *Context) {
return func(ctx *Context) { return func(ctx *Context) {
if !ctx.IsSigned || !ctx.Repo.IsAdmin() { if !ctx.IsSigned || !ctx.Repo.IsAdmin() {
ctx.NotFound(ctx.Req.URL.RequestURI(), nil) ctx.NotFound("RequireRepoAdmin denies the request", 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)
return return
} }
} }
@ -42,75 +31,30 @@ func CanEnableEditor() func(ctx *Context) {
} }
} }
// RequireRepoWriterOr returns a middleware for requiring repository write to one of the unit permission // RequireUnitWriter returns a middleware for requiring repository write to one of the unit permission
func RequireRepoWriterOr(unitTypes ...unit.Type) func(ctx *Context) { func RequireUnitWriter(unitTypes ...unit.Type) func(ctx *Context) {
return func(ctx *Context) { return func(ctx *Context) {
for _, unitType := range unitTypes { for _, unitType := range unitTypes {
if ctx.Repo.CanWrite(unitType) { if ctx.Repo.CanWrite(unitType) {
return 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 // RequireUnitReader returns a middleware for requiring repository write to one of the unit permission
func RequireRepoReader(unitType unit.Type) func(ctx *Context) { func RequireUnitReader(unitTypes ...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) {
return func(ctx *Context) { return func(ctx *Context) {
for _, unitType := range unitTypes { for _, unitType := range unitTypes {
if ctx.Repo.CanRead(unitType) { if ctx.Repo.CanRead(unitType) {
return return
} }
} if unitType == unit.TypeCode && canWriteAsMaintainer(ctx) {
if log.IsTrace() { return
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)
} }
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)
} }
} }

@ -110,41 +110,51 @@ type RepoSettingForm struct {
EnablePrune bool EnablePrune bool
// Advanced settings // Advanced settings
EnableCode bool EnableCode bool
EnableWiki bool DefaultCodeEveryoneAccess string
EnableExternalWiki bool
DefaultWikiBranch string EnableWiki bool
DefaultWikiEveryoneAccess string EnableExternalWiki bool
ExternalWikiURL string DefaultWikiBranch string
DefaultWikiEveryoneAccess string
ExternalWikiURL string
EnableIssues bool EnableIssues bool
DefaultIssuesEveryoneAccess string
EnableExternalTracker bool EnableExternalTracker bool
ExternalTrackerURL string ExternalTrackerURL string
TrackerURLFormat string TrackerURLFormat string
TrackerIssueStyle string TrackerIssueStyle string
ExternalTrackerRegexpPattern string ExternalTrackerRegexpPattern string
EnableCloseIssuesViaCommitInAnyBranch bool EnableCloseIssuesViaCommitInAnyBranch bool
EnableProjects bool
ProjectsMode string EnableProjects bool
EnableReleases bool ProjectsMode string
EnablePackages bool
EnablePulls bool EnableReleases bool
EnableActions bool
PullsIgnoreWhitespace bool EnablePackages bool
PullsAllowMerge bool
PullsAllowRebase bool EnablePulls bool
PullsAllowRebaseMerge bool PullsIgnoreWhitespace bool
PullsAllowSquash bool PullsAllowMerge bool
PullsAllowFastForwardOnly bool PullsAllowRebase bool
PullsAllowManualMerge bool PullsAllowRebaseMerge bool
PullsDefaultMergeStyle string PullsAllowSquash bool
EnableAutodetectManualMerge bool PullsAllowFastForwardOnly bool
PullsAllowRebaseUpdate bool PullsAllowManualMerge bool
DefaultDeleteBranchAfterMerge bool PullsDefaultMergeStyle string
DefaultAllowMaintainerEdit bool EnableAutodetectManualMerge bool
EnableTimetracker bool PullsAllowRebaseUpdate bool
AllowOnlyContributorsToTrackTime bool DefaultDeleteBranchAfterMerge bool
EnableIssueDependencies bool DefaultAllowMaintainerEdit bool
IsArchived bool EnableTimetracker bool
AllowOnlyContributorsToTrackTime bool
EnableIssueDependencies bool
EnableActions bool
IsArchived bool
// Signing Settings // Signing Settings
TrustModel string TrustModel string

@ -302,6 +302,15 @@
<input class="enable-system" name="enable_code" type="checkbox"{{if $isCodeEnabled}} checked{{end}}> <input class="enable-system" name="enable_code" type="checkbox"{{if $isCodeEnabled}} checked{{end}}>
<label>{{ctx.Locale.Tr "repo.code.desc"}}</label> <label>{{ctx.Locale.Tr "repo.code.desc"}}</label>
</div> </div>
<div class="inline field tw-pl-4">
{{$unitCode := .Repository.MustGetUnit ctx ctx.Consts.RepoUnitTypeCode}}
<label>{{ctx.Locale.Tr "repo.settings.default_permission_everyone_access"}}</label>
<select name="default_code_everyone_access" class="ui selection dropdown">
{{/* everyone access mode is different from others, none means it is unset and won't be applied */}}
<option value="none" {{Iif (eq $unitCode.EveryoneAccessMode 0) "selected"}}>{{ctx.Locale.Tr "settings.permission_not_set"}}</option>
<option value="read" {{Iif (eq $unitCode.EveryoneAccessMode 1) "selected"}}>{{ctx.Locale.Tr "settings.permission_read"}}</option>
</select>
</div>
</div> </div>
{{$isInternalWikiEnabled := .Repository.UnitEnabled ctx ctx.Consts.RepoUnitTypeWiki}} {{$isInternalWikiEnabled := .Repository.UnitEnabled ctx ctx.Consts.RepoUnitTypeWiki}}
@ -331,7 +340,7 @@
</div> </div>
<div class="inline field"> <div class="inline field">
{{$unitInternalWiki := .Repository.MustGetUnit ctx ctx.Consts.RepoUnitTypeWiki}} {{$unitInternalWiki := .Repository.MustGetUnit ctx ctx.Consts.RepoUnitTypeWiki}}
<label>{{ctx.Locale.Tr "repo.settings.default_wiki_everyone_access"}}</label> <label>{{ctx.Locale.Tr "repo.settings.default_permission_everyone_access"}}</label>
<select name="default_wiki_everyone_access" class="ui selection dropdown"> <select name="default_wiki_everyone_access" class="ui selection dropdown">
{{/* everyone access mode is different from others, none means it is unset and won't be applied */}} {{/* everyone access mode is different from others, none means it is unset and won't be applied */}}
<option value="none" {{Iif (eq $unitInternalWiki.EveryoneAccessMode 0) "selected"}}>{{ctx.Locale.Tr "settings.permission_not_set"}}</option> <option value="none" {{Iif (eq $unitInternalWiki.EveryoneAccessMode 0) "selected"}}>{{ctx.Locale.Tr "settings.permission_not_set"}}</option>
@ -374,6 +383,15 @@
</div> </div>
</div> </div>
<div class="field tw-pl-4 {{if (.Repository.UnitEnabled ctx ctx.Consts.RepoUnitTypeExternalTracker)}}disabled{{end}}" id="internal_issue_box"> <div class="field tw-pl-4 {{if (.Repository.UnitEnabled ctx ctx.Consts.RepoUnitTypeExternalTracker)}}disabled{{end}}" id="internal_issue_box">
<div class="inline field">
{{$unitIssue := .Repository.MustGetUnit ctx ctx.Consts.RepoUnitTypeIssues}}
<label>{{ctx.Locale.Tr "repo.settings.default_permission_everyone_access"}}</label>
<select name="default_issues_everyone_access" class="ui selection dropdown">
{{/* everyone access mode is different from others, none means it is unset and won't be applied */}}
<option value="none" {{Iif (eq $unitIssue.EveryoneAccessMode 0) "selected"}}>{{ctx.Locale.Tr "settings.permission_not_set"}}</option>
<option value="read" {{Iif (eq $unitIssue.EveryoneAccessMode 1) "selected"}}>{{ctx.Locale.Tr "settings.permission_read"}}</option>
</select>
</div>
{{if .Repository.CanEnableTimetracker}} {{if .Repository.CanEnableTimetracker}}
<div class="field"> <div class="field">
<div class="ui checkbox"> <div class="ui checkbox">

Loading…
Cancel
Save