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/modules/optional"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
)
// 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.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.Repo, _ = repo_model.CountRepositories(ctx, repo_model.CountRepositoryOptions{})
stats.Counter.Watch, _ = e.Count(new(repo_model.Watch))

View File

@ -50,8 +50,8 @@ type SearchOrganizationsOptions struct {
// FindOrgOptions finds orgs options
type FindOrgOptions struct {
db.ListOptions
UserID int64
IncludePrivate bool
UserID int64
IncludeVisibility structs.VisibleType
}
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 {
var cond builder.Cond = builder.Eq{"`user`.`type`": user_model.UserTypeOrganization}
if opts.UserID > 0 {
cond = cond.And(builder.In("`user`.`id`", queryUserOrgIDs(opts.UserID, opts.IncludePrivate)))
}
if !opts.IncludePrivate {
cond = cond.And(builder.Eq{"`user`.visibility": structs.VisibleTypePublic})
cond = cond.And(builder.In("`user`.`id`", queryUserOrgIDs(opts.UserID, opts.IncludeVisibility == structs.VisibleTypePrivate)))
}
// public=0, limited=1, private=2
cond = cond.And(builder.Lte{"`user`.visibility": opts.IncludeVisibility})
return cond
}
@ -77,6 +76,16 @@ func (opts FindOrgOptions) ToOrders() string {
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
// are allowed to create repos.
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/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/structs"
"github.com/stretchr/testify/assert"
)
func TestCountOrganizations(t *testing.T) {
func TestOrgList(t *testing.T) {
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{})
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.Equal(t, expected, cnt)
}
func TestFindOrgs(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
func testFindOrgs(t *testing.T) {
orgs, err := db.Find[organization.Organization](db.DefaultContext, organization.FindOrgOptions{
UserID: 4,
IncludePrivate: true,
UserID: 4,
IncludeVisibility: structs.VisibleTypePrivate,
})
assert.NoError(t, err)
if assert.Len(t, orgs, 1) {
@ -36,22 +43,20 @@ func TestFindOrgs(t *testing.T) {
}
orgs, err = db.Find[organization.Organization](db.DefaultContext, organization.FindOrgOptions{
UserID: 4,
IncludePrivate: false,
UserID: 4,
})
assert.NoError(t, err)
assert.Empty(t, orgs)
total, err := db.Count[organization.Organization](db.DefaultContext, organization.FindOrgOptions{
UserID: 4,
IncludePrivate: true,
UserID: 4,
IncludeVisibility: structs.VisibleTypePrivate,
})
assert.NoError(t, err)
assert.EqualValues(t, 1, total)
}
func TestGetUserOrgsList(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
func testGetUserOrgsList(t *testing.T) {
orgs, err := organization.GetUserOrgsList(db.DefaultContext, &user_model.User{ID: 4})
assert.NoError(t, err)
if assert.Len(t, orgs, 1) {
@ -61,8 +66,7 @@ func TestGetUserOrgsList(t *testing.T) {
}
}
func TestLoadOrgListTeams(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
func testLoadOrgListTeams(t *testing.T) {
orgs, err := organization.GetUserOrgsList(db.DefaultContext, &user_model.User{ID: 4})
assert.NoError(t, err)
assert.Len(t, orgs, 1)
@ -71,3 +75,10 @@ func TestLoadOrgListTeams(t *testing.T) {
assert.Len(t, teamsMap, 1)
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) {
listOptions := utils.GetListOptions(ctx)
showPrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == u.ID)
opts := organization.FindOrgOptions{
ListOptions: listOptions,
UserID: u.ID,
IncludePrivate: showPrivate,
ListOptions: listOptions,
UserID: u.ID,
IncludeVisibility: organization.DoerViewOtherVisibility(ctx.Doer, u),
}
orgs, maxResults, err := db.FindAndCount[organization.Organization](ctx, opts)
if err != nil {

View File

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

View File

@ -47,13 +47,12 @@ func prepareContextForProfileBigAvatar(ctx *context.Context) {
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{
UserID: ctx.ContextUser.ID,
IncludePrivate: showPrivate,
UserID: ctx.ContextUser.ID,
IncludeVisibility: organization.DoerViewOtherVisibility(ctx.Doer, ctx.ContextUser),
ListOptions: db.ListOptions{
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,
},
})

View File

@ -76,8 +76,7 @@ func userProfile(ctx *context.Context) {
profileDbRepo, profileReadmeBlob := shared_user.FindOwnerProfileReadme(ctx, ctx.Doer)
showPrivate := ctx.IsSigned && (ctx.Doer.IsAdmin || ctx.Doer.ID == ctx.ContextUser.ID)
prepareUserProfileTabData(ctx, showPrivate, profileDbRepo, profileReadmeBlob)
prepareUserProfileTabData(ctx, profileDbRepo, profileReadmeBlob)
// 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
@ -90,7 +89,7 @@ func userProfile(ctx *context.Context) {
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 not a profile readme, the overview tab should be treated as the repositories tab
tab := ctx.FormString("tab")
@ -175,6 +174,7 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDb
case "activity":
date := ctx.FormString("date")
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{
RequestedUser: ctx.ContextUser,
Actor: ctx.Doer,
@ -265,8 +265,8 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDb
}
case "organizations":
orgs, count, err := db.FindAndCount[organization.Organization](ctx, organization.FindOrgOptions{
UserID: ctx.ContextUser.ID,
IncludePrivate: showPrivate,
UserID: ctx.ContextUser.ID,
IncludeVisibility: organization.DoerViewOtherVisibility(ctx.Doer, ctx.ContextUser),
ListOptions: db.ListOptions{
Page: page,
PageSize: pagingNum,

View File

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

View File

@ -16,7 +16,9 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"github.com/golang-jwt/jwt/v5"
)
@ -231,12 +233,11 @@ func NewAccessTokenResponse(ctx context.Context, grant *auth.OAuth2Grant, server
}, nil
}
// returns a list of "org" and "org:team" strings,
// that the given user is a part of.
// GetOAuthGroupsForUser returns a list of "org" and "org:team" strings, that the given user is a part of.
func GetOAuthGroupsForUser(ctx context.Context, user *user_model.User, onlyPublicGroups bool) ([]string, error) {
orgs, err := db.Find[org_model.Organization](ctx, org_model.FindOrgOptions{
UserID: user.ID,
IncludePrivate: !onlyPublicGroups,
UserID: user.ID,
IncludeVisibility: util.Iif(onlyPublicGroups, api.VisibleTypePublic, api.VisibleTypePrivate),
})
if err != nil {
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/setting"
"code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/agit"
asymkey_service "code.gitea.io/gitea/services/asymkey"
@ -177,8 +178,8 @@ func DeleteUser(ctx context.Context, u *user_model.User, purge bool) error {
PageSize: repo_model.RepositoryListDefaultPageSize,
Page: 1,
},
UserID: u.ID,
IncludePrivate: true,
UserID: u.ID,
IncludeVisibility: structs.VisibleTypePrivate,
})
if err != nil {
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"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/util"
@ -437,8 +438,8 @@ func TestLDAPGroupTeamSyncAddMember(t *testing.T) {
Name: gitLDAPUser.UserName,
})
usersOrgs, err := db.Find[organization.Organization](db.DefaultContext, organization.FindOrgOptions{
UserID: user.ID,
IncludePrivate: true,
UserID: user.ID,
IncludeVisibility: structs.VisibleTypePrivate,
})
assert.NoError(t, err)
allOrgTeams, err := organization.GetUserOrgTeams(db.DefaultContext, org.ID, user.ID)

View File

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