mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 11:41:32 +01:00 
			
		
		
		
	Support public code/issue access for private repositories (#33127)
Close #8649, close #639 (will add "anonymous access" in following PRs)
This commit is contained in:
		
							parent
							
								
									ecd463c2f1
								
							
						
					
					
						commit
						a98a836e76
					
				| @ -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 | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -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) { | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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) { | ||||
|  | ||||
| @ -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 { | ||||
|  | ||||
| @ -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("/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.Post("/projects", reqRepoIssuesOrPullsWriter, reqRepoProjectsReader, repo.UpdateIssueProject) | ||||
| 			m.Post("/assignee", reqRepoIssuesOrPullsWriter, repo.UpdateIssueAssignee) | ||||
| 			m.Post("/status", reqRepoIssuesOrPullsWriter, repo.UpdateIssueStatus) | ||||
| 			m.Post("/delete", reqRepoAdmin, repo.BatchDeleteIssues) | ||||
| 			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()) | ||||
| 
 | ||||
|  | ||||
| @ -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 [" | ||||
| 			if unitType == unit.TypeCode && canWriteAsMaintainer(ctx) { | ||||
| 				return | ||||
| 			} | ||||
| 			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 | ||||
| 
 | ||||
| 	// 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 | ||||
|  | ||||
| @ -302,6 +302,15 @@ | ||||
| 						<input class="enable-system" name="enable_code" type="checkbox"{{if $isCodeEnabled}} checked{{end}}> | ||||
| 						<label>{{ctx.Locale.Tr "repo.code.desc"}}</label> | ||||
| 					</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> | ||||
| 
 | ||||
| 				{{$isInternalWikiEnabled := .Repository.UnitEnabled ctx ctx.Consts.RepoUnitTypeWiki}} | ||||
| @ -331,7 +340,7 @@ | ||||
| 						</div> | ||||
| 						<div class="inline field"> | ||||
| 							{{$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"> | ||||
| 								{{/* 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> | ||||
| @ -374,6 +383,15 @@ | ||||
| 						</div> | ||||
| 					</div> | ||||
| 					<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}} | ||||
| 							<div class="field"> | ||||
| 								<div class="ui checkbox"> | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user