mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-04 01:34:27 +00:00 
			
		
		
		
	Refactor FindOrgOptions to use enum instead of bool, fix membership visibility (#34629)
This commit is contained in:
		@@ -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))
 | 
			
		||||
 
 | 
			
		||||
@@ -51,7 +51,7 @@ type SearchOrganizationsOptions struct {
 | 
			
		||||
type FindOrgOptions struct {
 | 
			
		||||
	db.ListOptions
 | 
			
		||||
	UserID            int64
 | 
			
		||||
	IncludePrivate bool
 | 
			
		||||
	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) {
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
		IncludeVisibility: structs.VisibleTypePrivate,
 | 
			
		||||
	})
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	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{
 | 
			
		||||
		UserID: 4,
 | 
			
		||||
		IncludePrivate: false,
 | 
			
		||||
	})
 | 
			
		||||
	assert.NoError(t, err)
 | 
			
		||||
	assert.Empty(t, orgs)
 | 
			
		||||
 | 
			
		||||
	total, err := db.Count[organization.Organization](db.DefaultContext, organization.FindOrgOptions{
 | 
			
		||||
		UserID:            4,
 | 
			
		||||
		IncludePrivate: true,
 | 
			
		||||
		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}))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
		IncludeVisibility: organization.DoerViewOtherVisibility(ctx.Doer, u),
 | 
			
		||||
	}
 | 
			
		||||
	orgs, maxResults, err := db.FindAndCount[organization.Organization](ctx, opts)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
 
 | 
			
		||||
@@ -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"
 | 
			
		||||
@@ -294,7 +295,7 @@ func ViewUser(ctx *context.Context) {
 | 
			
		||||
	orgs, err := db.Find[org_model.Organization](ctx, org_model.FindOrgOptions{
 | 
			
		||||
		ListOptions:       db.ListOptionsAll,
 | 
			
		||||
		UserID:            u.ID,
 | 
			
		||||
		IncludePrivate: true,
 | 
			
		||||
		IncludeVisibility: structs.VisibleTypePrivate,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		ctx.ServerError("FindOrgs", err)
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
		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,
 | 
			
		||||
		},
 | 
			
		||||
	})
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
@@ -266,7 +266,7 @@ 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,
 | 
			
		||||
			IncludeVisibility: organization.DoerViewOtherVisibility(ctx.Doer, ctx.ContextUser),
 | 
			
		||||
			ListOptions: db.ListOptions{
 | 
			
		||||
				Page:     page,
 | 
			
		||||
				PageSize: pagingNum,
 | 
			
		||||
 
 | 
			
		||||
@@ -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"
 | 
			
		||||
@@ -207,7 +208,7 @@ func Organization(ctx *context.Context) {
 | 
			
		||||
			Page:     ctx.FormInt("page"),
 | 
			
		||||
		},
 | 
			
		||||
		UserID:            ctx.Doer.ID,
 | 
			
		||||
		IncludePrivate: ctx.IsSigned,
 | 
			
		||||
		IncludeVisibility: structs.VisibleTypePrivate,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if opts.Page <= 0 {
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
		IncludeVisibility: util.Iif(onlyPublicGroups, api.VisibleTypePublic, api.VisibleTypePrivate),
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("GetUserOrgList: %w", err)
 | 
			
		||||
 
 | 
			
		||||
@@ -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"
 | 
			
		||||
@@ -178,7 +179,7 @@ func DeleteUser(ctx context.Context, u *user_model.User, purge bool) error {
 | 
			
		||||
					Page:     1,
 | 
			
		||||
				},
 | 
			
		||||
				UserID:            u.ID,
 | 
			
		||||
				IncludePrivate: true,
 | 
			
		||||
				IncludeVisibility: structs.VisibleTypePrivate,
 | 
			
		||||
			})
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return fmt.Errorf("unable to find org list for %s[%d]. Error: %w", u.Name, u.ID, err)
 | 
			
		||||
 
 | 
			
		||||
@@ -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"
 | 
			
		||||
@@ -438,7 +439,7 @@ func TestLDAPGroupTeamSyncAddMember(t *testing.T) {
 | 
			
		||||
		})
 | 
			
		||||
		usersOrgs, err := db.Find[organization.Organization](db.DefaultContext, organization.FindOrgOptions{
 | 
			
		||||
			UserID:            user.ID,
 | 
			
		||||
			IncludePrivate: true,
 | 
			
		||||
			IncludeVisibility: structs.VisibleTypePrivate,
 | 
			
		||||
		})
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
		allOrgTeams, err := organization.GetUserOrgTeams(db.DefaultContext, org.ID, user.ID)
 | 
			
		||||
 
 | 
			
		||||
@@ -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{
 | 
			
		||||
			UserID:            user.ID,
 | 
			
		||||
			IncludePrivate: true,
 | 
			
		||||
			IncludeVisibility: api.VisibleTypePrivate,
 | 
			
		||||
		})
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user