Refactor FindOrgOptions to use enum instead of bool, fix membership visibility (#34629)

pull/34562/head^2
wxiaoguang 2025-06-09 11:30:34 +08:00 committed by GitHub
parent 1fe652cd26
commit f6041441ee
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 74 additions and 51 deletions

View File

@ -19,6 +19,7 @@ import (
"code.gitea.io/gitea/models/webhook" "code.gitea.io/gitea/models/webhook"
"code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
) )
// Statistic contains the database statistics // Statistic contains the database statistics
@ -68,7 +69,7 @@ func GetStatistic(ctx context.Context) (stats Statistic) {
} }
stats.Counter.UsersNotActive = user_model.CountUsers(ctx, &usersNotActiveOpts) stats.Counter.UsersNotActive = user_model.CountUsers(ctx, &usersNotActiveOpts)
stats.Counter.Org, _ = db.Count[organization.Organization](ctx, organization.FindOrgOptions{IncludePrivate: true}) stats.Counter.Org, _ = db.Count[organization.Organization](ctx, organization.FindOrgOptions{IncludeVisibility: structs.VisibleTypePrivate})
stats.Counter.PublicKey, _ = e.Count(new(asymkey_model.PublicKey)) stats.Counter.PublicKey, _ = e.Count(new(asymkey_model.PublicKey))
stats.Counter.Repo, _ = repo_model.CountRepositories(ctx, repo_model.CountRepositoryOptions{}) stats.Counter.Repo, _ = repo_model.CountRepositories(ctx, repo_model.CountRepositoryOptions{})
stats.Counter.Watch, _ = e.Count(new(repo_model.Watch)) stats.Counter.Watch, _ = e.Count(new(repo_model.Watch))

View File

@ -51,7 +51,7 @@ type SearchOrganizationsOptions struct {
type FindOrgOptions struct { type FindOrgOptions struct {
db.ListOptions db.ListOptions
UserID int64 UserID int64
IncludePrivate bool IncludeVisibility structs.VisibleType
} }
func queryUserOrgIDs(userID int64, includePrivate bool) *builder.Builder { func queryUserOrgIDs(userID int64, includePrivate bool) *builder.Builder {
@ -65,11 +65,10 @@ func queryUserOrgIDs(userID int64, includePrivate bool) *builder.Builder {
func (opts FindOrgOptions) ToConds() builder.Cond { func (opts FindOrgOptions) ToConds() builder.Cond {
var cond builder.Cond = builder.Eq{"`user`.`type`": user_model.UserTypeOrganization} var cond builder.Cond = builder.Eq{"`user`.`type`": user_model.UserTypeOrganization}
if opts.UserID > 0 { if opts.UserID > 0 {
cond = cond.And(builder.In("`user`.`id`", queryUserOrgIDs(opts.UserID, opts.IncludePrivate))) cond = cond.And(builder.In("`user`.`id`", queryUserOrgIDs(opts.UserID, opts.IncludeVisibility == structs.VisibleTypePrivate)))
}
if !opts.IncludePrivate {
cond = cond.And(builder.Eq{"`user`.visibility": structs.VisibleTypePublic})
} }
// public=0, limited=1, private=2
cond = cond.And(builder.Lte{"`user`.visibility": opts.IncludeVisibility})
return cond return cond
} }
@ -77,6 +76,16 @@ func (opts FindOrgOptions) ToOrders() string {
return "`user`.lower_name ASC" return "`user`.lower_name ASC"
} }
func DoerViewOtherVisibility(doer, other *user_model.User) structs.VisibleType {
if doer == nil || other == nil {
return structs.VisibleTypePublic
}
if doer.IsAdmin || doer.ID == other.ID {
return structs.VisibleTypePrivate
}
return structs.VisibleTypeLimited
}
// GetOrgsCanCreateRepoByUserID returns a list of organizations where given user ID // GetOrgsCanCreateRepoByUserID returns a list of organizations where given user ID
// are allowed to create repos. // are allowed to create repos.
func GetOrgsCanCreateRepoByUserID(ctx context.Context, userID int64) ([]*Organization, error) { func GetOrgsCanCreateRepoByUserID(ctx context.Context, userID int64) ([]*Organization, error) {

View File

@ -10,25 +10,32 @@ import (
"code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/structs"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestCountOrganizations(t *testing.T) { func TestOrgList(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
t.Run("CountOrganizations", testCountOrganizations)
t.Run("FindOrgs", testFindOrgs)
t.Run("GetUserOrgsList", testGetUserOrgsList)
t.Run("LoadOrgListTeams", testLoadOrgListTeams)
t.Run("DoerViewOtherVisibility", testDoerViewOtherVisibility)
}
func testCountOrganizations(t *testing.T) {
expected, err := db.GetEngine(db.DefaultContext).Where("type=?", user_model.UserTypeOrganization).Count(&organization.Organization{}) expected, err := db.GetEngine(db.DefaultContext).Where("type=?", user_model.UserTypeOrganization).Count(&organization.Organization{})
assert.NoError(t, err) assert.NoError(t, err)
cnt, err := db.Count[organization.Organization](db.DefaultContext, organization.FindOrgOptions{IncludePrivate: true}) cnt, err := db.Count[organization.Organization](db.DefaultContext, organization.FindOrgOptions{IncludeVisibility: structs.VisibleTypePrivate})
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, expected, cnt) assert.Equal(t, expected, cnt)
} }
func TestFindOrgs(t *testing.T) { func testFindOrgs(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
orgs, err := db.Find[organization.Organization](db.DefaultContext, organization.FindOrgOptions{ orgs, err := db.Find[organization.Organization](db.DefaultContext, organization.FindOrgOptions{
UserID: 4, UserID: 4,
IncludePrivate: true, IncludeVisibility: structs.VisibleTypePrivate,
}) })
assert.NoError(t, err) assert.NoError(t, err)
if assert.Len(t, orgs, 1) { if assert.Len(t, orgs, 1) {
@ -37,21 +44,19 @@ func TestFindOrgs(t *testing.T) {
orgs, err = db.Find[organization.Organization](db.DefaultContext, organization.FindOrgOptions{ orgs, err = db.Find[organization.Organization](db.DefaultContext, organization.FindOrgOptions{
UserID: 4, UserID: 4,
IncludePrivate: false,
}) })
assert.NoError(t, err) assert.NoError(t, err)
assert.Empty(t, orgs) assert.Empty(t, orgs)
total, err := db.Count[organization.Organization](db.DefaultContext, organization.FindOrgOptions{ total, err := db.Count[organization.Organization](db.DefaultContext, organization.FindOrgOptions{
UserID: 4, UserID: 4,
IncludePrivate: true, IncludeVisibility: structs.VisibleTypePrivate,
}) })
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, 1, total) assert.EqualValues(t, 1, total)
} }
func TestGetUserOrgsList(t *testing.T) { func testGetUserOrgsList(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
orgs, err := organization.GetUserOrgsList(db.DefaultContext, &user_model.User{ID: 4}) orgs, err := organization.GetUserOrgsList(db.DefaultContext, &user_model.User{ID: 4})
assert.NoError(t, err) assert.NoError(t, err)
if assert.Len(t, orgs, 1) { if assert.Len(t, orgs, 1) {
@ -61,8 +66,7 @@ func TestGetUserOrgsList(t *testing.T) {
} }
} }
func TestLoadOrgListTeams(t *testing.T) { func testLoadOrgListTeams(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
orgs, err := organization.GetUserOrgsList(db.DefaultContext, &user_model.User{ID: 4}) orgs, err := organization.GetUserOrgsList(db.DefaultContext, &user_model.User{ID: 4})
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, orgs, 1) assert.Len(t, orgs, 1)
@ -71,3 +75,10 @@ func TestLoadOrgListTeams(t *testing.T) {
assert.Len(t, teamsMap, 1) assert.Len(t, teamsMap, 1)
assert.Len(t, teamsMap[3], 5) assert.Len(t, teamsMap[3], 5)
} }
func testDoerViewOtherVisibility(t *testing.T) {
assert.Equal(t, structs.VisibleTypePublic, organization.DoerViewOtherVisibility(nil, nil))
assert.Equal(t, structs.VisibleTypeLimited, organization.DoerViewOtherVisibility(&user_model.User{ID: 1}, &user_model.User{ID: 2}))
assert.Equal(t, structs.VisibleTypePrivate, organization.DoerViewOtherVisibility(&user_model.User{ID: 1}, &user_model.User{ID: 1}))
assert.Equal(t, structs.VisibleTypePrivate, organization.DoerViewOtherVisibility(&user_model.User{ID: 1, IsAdmin: true}, &user_model.User{ID: 2}))
}

View File

@ -26,12 +26,10 @@ import (
func listUserOrgs(ctx *context.APIContext, u *user_model.User) { func listUserOrgs(ctx *context.APIContext, u *user_model.User) {
listOptions := utils.GetListOptions(ctx) listOptions := utils.GetListOptions(ctx)
showPrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == u.ID)
opts := organization.FindOrgOptions{ opts := organization.FindOrgOptions{
ListOptions: listOptions, ListOptions: listOptions,
UserID: u.ID, UserID: u.ID,
IncludePrivate: showPrivate, IncludeVisibility: organization.DoerViewOtherVisibility(ctx.Doer, u),
} }
orgs, maxResults, err := db.FindAndCount[organization.Organization](ctx, opts) orgs, maxResults, err := db.FindAndCount[organization.Organization](ctx, opts)
if err != nil { if err != nil {

View File

@ -21,6 +21,7 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/web/explore" "code.gitea.io/gitea/routers/web/explore"
@ -294,7 +295,7 @@ func ViewUser(ctx *context.Context) {
orgs, err := db.Find[org_model.Organization](ctx, org_model.FindOrgOptions{ orgs, err := db.Find[org_model.Organization](ctx, org_model.FindOrgOptions{
ListOptions: db.ListOptionsAll, ListOptions: db.ListOptionsAll,
UserID: u.ID, UserID: u.ID,
IncludePrivate: true, IncludeVisibility: structs.VisibleTypePrivate,
}) })
if err != nil { if err != nil {
ctx.ServerError("FindOrgs", err) ctx.ServerError("FindOrgs", err)

View File

@ -47,13 +47,12 @@ func prepareContextForProfileBigAvatar(ctx *context.Context) {
ctx.Data["RenderedDescription"] = content ctx.Data["RenderedDescription"] = content
} }
showPrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == ctx.ContextUser.ID)
orgs, err := db.Find[organization.Organization](ctx, organization.FindOrgOptions{ orgs, err := db.Find[organization.Organization](ctx, organization.FindOrgOptions{
UserID: ctx.ContextUser.ID, UserID: ctx.ContextUser.ID,
IncludePrivate: showPrivate, IncludeVisibility: organization.DoerViewOtherVisibility(ctx.Doer, ctx.ContextUser),
ListOptions: db.ListOptions{ ListOptions: db.ListOptions{
Page: 1, Page: 1,
// query one more results (without a separate counting) to see whether we need to add the "show more orgs" link // query one more result (without a separate counting) to see whether we need to add the "show more orgs" link
PageSize: setting.UI.User.OrgPagingNum + 1, PageSize: setting.UI.User.OrgPagingNum + 1,
}, },
}) })

View File

@ -76,8 +76,7 @@ func userProfile(ctx *context.Context) {
profileDbRepo, profileReadmeBlob := shared_user.FindOwnerProfileReadme(ctx, ctx.Doer) profileDbRepo, profileReadmeBlob := shared_user.FindOwnerProfileReadme(ctx, ctx.Doer)
showPrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == ctx.ContextUser.ID) prepareUserProfileTabData(ctx, profileDbRepo, profileReadmeBlob)
prepareUserProfileTabData(ctx, showPrivate, profileDbRepo, profileReadmeBlob)
// prepare the user nav header data after "prepareUserProfileTabData" to avoid re-querying the NumFollowers & NumFollowing // prepare the user nav header data after "prepareUserProfileTabData" to avoid re-querying the NumFollowers & NumFollowing
// because ctx.Data["NumFollowers"] and "NumFollowing" logic duplicates in both of them // because ctx.Data["NumFollowers"] and "NumFollowing" logic duplicates in both of them
@ -90,7 +89,7 @@ func userProfile(ctx *context.Context) {
ctx.HTML(http.StatusOK, tplProfile) ctx.HTML(http.StatusOK, tplProfile)
} }
func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDbRepo *repo_model.Repository, profileReadme *git.Blob) { func prepareUserProfileTabData(ctx *context.Context, profileDbRepo *repo_model.Repository, profileReadme *git.Blob) {
// if there is a profile readme, default to "overview" page, otherwise, default to "repositories" page // if there is a profile readme, default to "overview" page, otherwise, default to "repositories" page
// if there is not a profile readme, the overview tab should be treated as the repositories tab // if there is not a profile readme, the overview tab should be treated as the repositories tab
tab := ctx.FormString("tab") tab := ctx.FormString("tab")
@ -175,6 +174,7 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDb
case "activity": case "activity":
date := ctx.FormString("date") date := ctx.FormString("date")
pagingNum = setting.UI.FeedPagingNum pagingNum = setting.UI.FeedPagingNum
showPrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == ctx.ContextUser.ID)
items, count, err := feed_service.GetFeeds(ctx, activities_model.GetFeedsOptions{ items, count, err := feed_service.GetFeeds(ctx, activities_model.GetFeedsOptions{
RequestedUser: ctx.ContextUser, RequestedUser: ctx.ContextUser,
Actor: ctx.Doer, Actor: ctx.Doer,
@ -266,7 +266,7 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDb
case "organizations": case "organizations":
orgs, count, err := db.FindAndCount[organization.Organization](ctx, organization.FindOrgOptions{ orgs, count, err := db.FindAndCount[organization.Organization](ctx, organization.FindOrgOptions{
UserID: ctx.ContextUser.ID, UserID: ctx.ContextUser.ID,
IncludePrivate: showPrivate, IncludeVisibility: organization.DoerViewOtherVisibility(ctx.Doer, ctx.ContextUser),
ListOptions: db.ListOptions{ ListOptions: db.ListOptions{
Page: page, Page: page,
PageSize: pagingNum, PageSize: pagingNum,

View File

@ -22,6 +22,7 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/translation" "code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/typesniffer" "code.gitea.io/gitea/modules/typesniffer"
@ -207,7 +208,7 @@ func Organization(ctx *context.Context) {
Page: ctx.FormInt("page"), Page: ctx.FormInt("page"),
}, },
UserID: ctx.Doer.ID, UserID: ctx.Doer.ID,
IncludePrivate: ctx.IsSigned, IncludeVisibility: structs.VisibleTypePrivate,
} }
if opts.Page <= 0 { if opts.Page <= 0 {

View File

@ -16,7 +16,9 @@ import (
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"github.com/golang-jwt/jwt/v5" "github.com/golang-jwt/jwt/v5"
) )
@ -231,12 +233,11 @@ func NewAccessTokenResponse(ctx context.Context, grant *auth.OAuth2Grant, server
}, nil }, nil
} }
// returns a list of "org" and "org:team" strings, // GetOAuthGroupsForUser returns a list of "org" and "org:team" strings, that the given user is a part of.
// that the given user is a part of.
func GetOAuthGroupsForUser(ctx context.Context, user *user_model.User, onlyPublicGroups bool) ([]string, error) { func GetOAuthGroupsForUser(ctx context.Context, user *user_model.User, onlyPublicGroups bool) ([]string, error) {
orgs, err := db.Find[org_model.Organization](ctx, org_model.FindOrgOptions{ orgs, err := db.Find[org_model.Organization](ctx, org_model.FindOrgOptions{
UserID: user.ID, UserID: user.ID,
IncludePrivate: !onlyPublicGroups, IncludeVisibility: util.Iif(onlyPublicGroups, api.VisibleTypePublic, api.VisibleTypePrivate),
}) })
if err != nil { if err != nil {
return nil, fmt.Errorf("GetUserOrgList: %w", err) return nil, fmt.Errorf("GetUserOrgList: %w", err)

View File

@ -20,6 +20,7 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/agit" "code.gitea.io/gitea/services/agit"
asymkey_service "code.gitea.io/gitea/services/asymkey" asymkey_service "code.gitea.io/gitea/services/asymkey"
@ -178,7 +179,7 @@ func DeleteUser(ctx context.Context, u *user_model.User, purge bool) error {
Page: 1, Page: 1,
}, },
UserID: u.ID, UserID: u.ID,
IncludePrivate: true, IncludeVisibility: structs.VisibleTypePrivate,
}) })
if err != nil { if err != nil {
return fmt.Errorf("unable to find org list for %s[%d]. Error: %w", u.Name, u.ID, err) return fmt.Errorf("unable to find org list for %s[%d]. Error: %w", u.Name, u.ID, err)

View File

@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/translation" "code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
@ -438,7 +439,7 @@ func TestLDAPGroupTeamSyncAddMember(t *testing.T) {
}) })
usersOrgs, err := db.Find[organization.Organization](db.DefaultContext, organization.FindOrgOptions{ usersOrgs, err := db.Find[organization.Organization](db.DefaultContext, organization.FindOrgOptions{
UserID: user.ID, UserID: user.ID,
IncludePrivate: true, IncludeVisibility: structs.VisibleTypePrivate,
}) })
assert.NoError(t, err) assert.NoError(t, err)
allOrgTeams, err := organization.GetUserOrgTeams(db.DefaultContext, org.ID, user.ID) allOrgTeams, err := organization.GetUserOrgTeams(db.DefaultContext, org.ID, user.ID)

View File

@ -121,7 +121,7 @@ func doCheckOrgCounts(username string, orgCounts map[string]int, strict bool, ca
orgs, err := db.Find[organization.Organization](db.DefaultContext, organization.FindOrgOptions{ orgs, err := db.Find[organization.Organization](db.DefaultContext, organization.FindOrgOptions{
UserID: user.ID, UserID: user.ID,
IncludePrivate: true, IncludeVisibility: api.VisibleTypePrivate,
}) })
assert.NoError(t, err) assert.NoError(t, err)