mirror of
https://github.com/go-gitea/gitea.git
synced 2026-08-21 21:30:36 +00:00
Backport #38992 by @lunny Both issue search endpoints resolve their repository filter with `SearchRepositoryIDs` and pass the result to the indexer as `RepoIDs`. They mean to leave public repositories to the indexer, but `SearchRepoOptions.AllPublic` is only read when `OwnerID > 0`, so without an `owner` filter the flag does nothing and every public repository is enumerated, without a `LIMIT`, into `repo_id IN (...)`. Those IDs are redundant, as `allPublic` is passed to the indexer, which already matches every public repository. On a large instance this binds tens of thousands of parameters and can fail in the driver, making the endpoint return 500 for every filter. Admins are worst hit, as `SearchRepositoryCondition` skips their accessible-repository condition and enumerates the whole table. Restrict the enumeration to private repositories. The result set is unchanged, as the dropped IDs are a subset of what `allPublic` matches. Both endpoints held copies of this block, so it moves to `routers/common`. Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
@@ -5,6 +5,7 @@ package repo
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"maps"
|
||||
"net/http"
|
||||
"slices"
|
||||
@@ -15,7 +16,6 @@ import (
|
||||
"gitea.dev/models/db"
|
||||
git_model "gitea.dev/models/git"
|
||||
issues_model "gitea.dev/models/issues"
|
||||
"gitea.dev/models/organization"
|
||||
repo_model "gitea.dev/models/repo"
|
||||
"gitea.dev/models/unit"
|
||||
user_model "gitea.dev/models/user"
|
||||
@@ -56,85 +56,22 @@ func SearchIssues(ctx *context.Context) {
|
||||
|
||||
isClosed := common.ParseIssueFilterStateIsClosed(ctx.FormString("state"))
|
||||
|
||||
var (
|
||||
repoIDs []int64
|
||||
allPublic bool
|
||||
)
|
||||
{
|
||||
// find repos user can access (for issue search)
|
||||
opts := repo_model.SearchRepoOptions{
|
||||
Private: false,
|
||||
AllPublic: true,
|
||||
TopicOnly: false,
|
||||
Collaborate: optional.None[bool](),
|
||||
// This needs to be a column that is not nil in fixtures or
|
||||
// MySQL will return different results when sorting by null in some cases
|
||||
OrderBy: db.SearchOrderByAlphabetically,
|
||||
Actor: ctx.Doer,
|
||||
}
|
||||
if ctx.IsSigned {
|
||||
opts.Private = true
|
||||
opts.AllLimited = true
|
||||
}
|
||||
if ctx.FormString("owner") != "" {
|
||||
owner, err := user_model.GetUserByName(ctx, ctx.FormString("owner"))
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
ctx.HTTPError(http.StatusBadRequest, "Owner not found", err.Error())
|
||||
} else {
|
||||
ctx.HTTPError(http.StatusInternalServerError, "GetUserByName", err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
opts.OwnerID = owner.ID
|
||||
opts.AllLimited = false
|
||||
opts.AllPublic = false
|
||||
opts.Collaborate = optional.Some(false)
|
||||
}
|
||||
if ctx.FormString("team") != "" {
|
||||
if ctx.FormString("owner") == "" {
|
||||
ctx.HTTPError(http.StatusBadRequest, "", "Owner organisation is required for filtering on team")
|
||||
return
|
||||
}
|
||||
team, err := organization.GetTeam(ctx, opts.OwnerID, ctx.FormString("team"))
|
||||
if err != nil {
|
||||
if organization.IsErrTeamNotExist(err) {
|
||||
ctx.HTTPError(http.StatusBadRequest, "Team not found", err.Error())
|
||||
} else {
|
||||
ctx.HTTPError(http.StatusInternalServerError, "GetUserByName", err.Error())
|
||||
}
|
||||
return
|
||||
}
|
||||
opts.TeamID = team.ID
|
||||
}
|
||||
|
||||
if opts.AllPublic {
|
||||
allPublic = true
|
||||
opts.AllPublic = false // set it false to avoid returning too many repos, we could filter by indexer
|
||||
}
|
||||
repoIDs, _, err = repo_model.SearchRepositoryIDs(ctx, opts)
|
||||
if err != nil {
|
||||
ctx.HTTPError(http.StatusInternalServerError, "SearchRepositoryIDs", err.Error())
|
||||
return
|
||||
}
|
||||
if len(repoIDs) == 0 {
|
||||
// no repos found, don't let the indexer return all repos
|
||||
repoIDs = []int64{0}
|
||||
repoIDs, allPublic, err := common.SearchIssuesRepoIDs(ctx, common.SearchIssuesRepoIDsOptions{
|
||||
Doer: ctx.Doer,
|
||||
OwnerName: ctx.FormString("owner"),
|
||||
TeamName: ctx.FormString("team"),
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) || errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.HTTPError(http.StatusBadRequest, err.Error())
|
||||
} else {
|
||||
ctx.ServerError("SearchIssuesRepoIDs", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
keyword := ctx.FormTrim("q")
|
||||
if strings.IndexByte(keyword, 0) >= 0 {
|
||||
keyword = ""
|
||||
}
|
||||
|
||||
isPull := optional.None[bool]()
|
||||
switch ctx.FormString("type") {
|
||||
case "pulls":
|
||||
isPull = optional.Some(true)
|
||||
case "issues":
|
||||
isPull = optional.Some(false)
|
||||
}
|
||||
isPull := common.ParseIssueFilterTypeIsPull(ctx.FormString("type"))
|
||||
|
||||
var includedAnyLabels []int64
|
||||
{
|
||||
@@ -145,7 +82,7 @@ func SearchIssues(ctx *context.Context) {
|
||||
}
|
||||
includedAnyLabels, err = issues_model.GetLabelIDsByNames(ctx, includedLabelNames)
|
||||
if err != nil {
|
||||
ctx.HTTPError(http.StatusInternalServerError, "GetLabelIDsByNames", err.Error())
|
||||
ctx.ServerError("GetLabelIDsByNames", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -159,7 +96,7 @@ func SearchIssues(ctx *context.Context) {
|
||||
}
|
||||
includedMilestones, err = issues_model.GetMilestoneIDsByNames(ctx, includedMilestoneNames)
|
||||
if err != nil {
|
||||
ctx.HTTPError(http.StatusInternalServerError, "GetMilestoneIDsByNames", err.Error())
|
||||
ctx.ServerError("GetMilestoneIDsByNames", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -223,12 +160,12 @@ func SearchIssues(ctx *context.Context) {
|
||||
|
||||
ids, total, err := issue_indexer.SearchIssues(ctx, searchOpt)
|
||||
if err != nil {
|
||||
ctx.HTTPError(http.StatusInternalServerError, "SearchIssues", err.Error())
|
||||
ctx.ServerError("SearchIssues", err)
|
||||
return
|
||||
}
|
||||
issues, err := issues_model.GetIssuesByIDs(ctx, ids, true)
|
||||
if err != nil {
|
||||
ctx.HTTPError(http.StatusInternalServerError, "FindIssuesByIDs", err.Error())
|
||||
ctx.ServerError("FindIssuesByIDs", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -267,11 +204,7 @@ func SearchRepoIssuesJSON(ctx *context.Context) {
|
||||
}
|
||||
|
||||
isClosed := common.ParseIssueFilterStateIsClosed(ctx.FormString("state"))
|
||||
|
||||
keyword := ctx.FormTrim("q")
|
||||
if strings.IndexByte(keyword, 0) >= 0 {
|
||||
keyword = ""
|
||||
}
|
||||
|
||||
var mileIDs []int64
|
||||
if part := strings.Split(ctx.FormString("milestones"), ","); len(part) > 0 {
|
||||
@@ -303,13 +236,7 @@ func SearchRepoIssuesJSON(ctx *context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
isPull := optional.None[bool]()
|
||||
switch ctx.FormString("type") {
|
||||
case "pulls":
|
||||
isPull = optional.Some(true)
|
||||
case "issues":
|
||||
isPull = optional.Some(false)
|
||||
}
|
||||
isPull := common.ParseIssueFilterTypeIsPull(ctx.FormString("type"))
|
||||
|
||||
// FIXME: we should be more efficient here
|
||||
createdByID := getUserIDForFilter(ctx, "created_by")
|
||||
|
||||
Reference in New Issue
Block a user