0
0
mirror of https://github.com/go-gitea/gitea.git synced 2026-05-11 04:55:34 +02:00

refactor: update group model package

- add `CanAccessUnitAtLevel` method to check if a user has access to a unit in a group
- update `CanAccessAtLevel` to wrap `CanAccessUnitAtLevel`
- update `IsOwnedBy`, `IsAdminOf` and `CanCreateIn` methods to use existing cond builders
- update `universalGroupPermBuilder` func to take a flag that dictates whether admin users should be taken into consideration
- add `IsPrivateBecauseOfParentPermissions` method that checks if a group should be hidden because its parent is private/inaccessible
- add `GetGroupByIDAndCond` method to find a group matching an ID and cond
- make `GetGroupByRepoID` more efficient by replacing `where ... in` with join
- ensure that `MoveGroup` updates each subgroup's sort order in the new parent
- add `GetOwnerByGroupID` func that returns the owner of a group
This commit is contained in:
☙◦ The Tablet ❀ GamerGirlandCo ◦❧ 2026-05-08 18:28:05 -04:00
parent a8e886822a
commit dc376ee7e7
No known key found for this signature in database
GPG Key ID: 924A5F6AF051E87C
2 changed files with 161 additions and 47 deletions

View File

@ -137,40 +137,68 @@ func (g *Group) CanAccess(ctx context.Context, user *user_model.User) (bool, err
}
func (g *Group) CanAccessAtLevel(ctx context.Context, user *user_model.User, level perm.AccessMode) (bool, error) {
orCond := builder.Or(AccessibleGroupCondition(user, g.OwnerID, unit.TypeInvalid, level))
return g.CanAccessUnitAtLevel(ctx, user, unit.TypeInvalid, level)
}
func (g *Group) CanAccessUnitAtLevel(ctx context.Context, user *user_model.User, u unit.Type, level perm.AccessMode) (bool, error) {
if user != nil {
ownedBy, err := g.IsOwnedBy(ctx, user.ID)
if err != nil {
return false, err
}
if ownedBy {
return true, nil
}
}
orCond := builder.Or(AccessibleGroupCondition(user, g.OwnerID, u, level))
if level == perm.AccessModeRead {
orCond = orCond.Or(builder.Eq{"`repo_group`.visibility": structs.VisibleTypePublic})
}
return db.GetEngine(ctx).Table(g.TableName()).Where(orCond.And(builder.Eq{"`repo_group`.id": g.ID})).Exist()
return db.GetEngine(ctx).Table(g.TableName()).Where(builder.And(builder.Eq{"`repo_group`.id": g.ID}, orCond)).Exist()
}
func (g *Group) IsOwnedBy(ctx context.Context, userID int64) (bool, error) {
return db.GetEngine(ctx).
Where("team_user.uid = ?", userID).
Join("INNER", "team_user", "team_user.team_id = repo_group_team.team_id").
And("repo_group_team.access_mode = ?", perm.AccessModeOwner).
And("repo_group_team.group_id = ?", g.ID).
Table("repo_group_team").
Where(
builder.Or(
UserOrgTeamPermCond("`repo_group`.id", userID, g.OwnerID, perm.AccessModeOwner),
universalGroupPermBuilder("`repo_group`.id", userID, g.OwnerID, false)).
And(builder.Eq{"`repo_group`.id": g.ID})).
Table(g.TableName()).
Exist()
}
func (g *Group) CanCreateIn(ctx context.Context, userID int64) (bool, error) {
return db.GetEngine(ctx).
Where("team_user.uid = ?", userID).
cond := builder.Eq{
"team_user.uid": userID,
"repo_group_team.group_id": g.ID,
"repo_group_team.can_create_in": true,
}
isAdmin, err := g.IsAdminOf(ctx, userID)
if err != nil {
return false, err
}
res, err := db.GetEngine(ctx).
Join("INNER", "team_user", "team_user.team_id = repo_group_team.team_id").
And("repo_group_team.group_id = ?", g.ID).
And("repo_group_team.can_create_in = ?", true).
Where(cond).
Table("repo_group_team").
Exist()
if err != nil {
return false, err
}
return isAdmin || res, nil
}
func (g *Group) IsAdminOf(ctx context.Context, userID int64) (bool, error) {
return db.GetEngine(ctx).
Where("team_user.uid = ?", userID).
Join("INNER", "team_user", "team_user.team_id = repo_group_team.team_id").
And("repo_group_team.group_id = ?", g.ID).
And("repo_group_team.access_mode >= ?", perm.AccessModeAdmin).
Table("repo_group_team").
Where(
builder.Or(
UserOrgTeamPermCond("`repo_group`.id", userID, g.OwnerID, perm.AccessModeAdmin),
universalGroupPermBuilder("`repo_group`.id", userID, g.OwnerID, false)).
And(builder.Eq{"`repo_group`.id": g.ID})).
Table(g.TableName()).
Exist()
}
@ -178,10 +206,20 @@ func (g *Group) ShortName(length int) string {
return util.EllipsisDisplayString(g.Name, length)
}
func GetGroupByID(ctx context.Context, id int64) (*Group, error) {
func (g *Group) IsPrivateBecauseOfParentPermissions(ctx context.Context, user *user_model.User) (bool, error) {
cond := AccessibleParentGroupCond(ctx, "`repo_group`.`id`", g.ID, user)
has, err := db.GetEngine(ctx).Where(cond.And(builder.Eq{
"`repo_group`.id": g.ID,
})).Table(g.TableName()).Exist()
return !has, err
}
func GetGroupByIDAndCond(ctx context.Context, id int64, cond builder.Cond) (*Group, error) {
group := new(Group)
has, err := db.GetEngine(ctx).ID(id).Get(group)
has, err := db.GetEngine(ctx).
Where(cond.And(builder.Eq{"`repo_group`.id": id})).Get(group)
if err != nil {
return nil, err
} else if !has {
@ -190,13 +228,15 @@ func GetGroupByID(ctx context.Context, id int64) (*Group, error) {
return group, nil
}
func GetGroupByID(ctx context.Context, id int64) (*Group, error) {
return GetGroupByIDAndCond(ctx, id, builder.Expr("1 = 1"))
}
func GetGroupByRepoID(ctx context.Context, repoID int64) (*Group, error) {
group := new(Group)
_, err := db.GetEngine(ctx).
In("id", builder.
Select("group_id").
From("repo").
Where(builder.Eq{"id": repoID})).
Join("INNER", "repository", "repository.group_id = repo_group.id").
Where(builder.Eq{"repository.`id`": repoID}).
Get(group)
return group, err
}
@ -324,6 +364,45 @@ func GetParentGroupIDChain(ctx context.Context, groupID int64) ([]int64, error)
return ids, err
}
func groupHierarchyCTEBuilder(cond builder.Cond) builder.Cond {
firstPart := builder.Select(fmt.Sprintf("repo_group.*"), "1 as depth").
From("repo_group").
Where(builder.And(builder.Eq{
"parent_group_id": 0,
}, cond))
secondPart := builder.Select("r.*", "h.depth + 1").
From("repo_group", "r").
Join("INNER", "group_hierarchy h", "r.parent_group_id = h.id")
firstSql, _ := firstPart.ToBoundSQL()
secondSql, _ := secondPart.ToBoundSQL()
return builder.Expr(firstSql + " UNION ALL " + secondSql)
}
func AccessibleParentGroupCond(ctx context.Context, idStr string, groupID int64, user *user_model.User) builder.Cond {
owner, err := GetOwnerByGroupID(ctx, groupID)
if err != nil {
return builder.Exists(builder.Select("1 as dummy").Where(builder.Eq{
"dummy": 1,
}))
}
accessibleCond := AccessibleGroupCondition(user, owner.ID, unit.TypeInvalid, perm.AccessModeRead)
unionBldr := groupHierarchyCTEBuilder(accessibleCond)
unionSql, err := builder.ToBoundSQL(unionBldr)
if err != nil {
}
s := db.GetEngine(ctx)
s.SQL("WITH RECURSIVE group_hierarchy AS ("+unionSql+") SELECT id from group_hierarchy", unionSql)
var g []*Group
err = s.Find(&g)
if err != nil {
log.Info("%s", err.Error())
}
return builder.In(idStr, builder.Expr("(WITH RECURSIVE group_hierarchy AS ("+unionSql+") SELECT id from group_hierarchy)"))
//db.GetEngine(ctx).SQL()
}
// ParentGroupCond returns a condition matching a group and its ancestors
func ParentGroupCond(ctx context.Context, idStr string, groupID int64) builder.Cond {
groupList, err := GetParentGroupIDChain(ctx, groupID)
@ -347,20 +426,49 @@ func MoveGroup(ctx context.Context, group *Group, newParent int64, newSortOrder
return err
}
var siblings RepoGroupList
var tmpSiblings RepoGroupList
if ng != nil {
if ng.OwnerID != group.OwnerID {
return fmt.Errorf("group[%d]'s ownerID is not equal to new parent group[%d]'s owner ID", group.ID, ng.ID)
}
if err = ng.LoadSubgroups(ctx, false); err != nil {
return err
}
siblings = append(append(ng.Subgroups[0:min(newSortOrder, len(ng.Subgroups))], group), ng.Subgroups[newSortOrder:]...)
} else if newParent <= 0 {
tmpSiblings, err = FindGroups(ctx, &FindGroupsOptions{
OwnerID: group.OwnerID,
ParentGroupID: 0,
})
tmpSiblings2 := make(RepoGroupList, newSortOrder)
copy(tmpSiblings2, tmpSiblings[0:newSortOrder])
tmpSiblings2 = append(tmpSiblings2, group)
siblings = append(tmpSiblings2, tmpSiblings[newSortOrder:]...)
}
parentGroupChain, err := GetParentGroupChain(ctx, newParent)
if err != nil {
return err
}
if len(parentGroupChain) >= 20 {
return ErrGroupTooDeep{
ID: group.ID,
}
}
group.ParentGroupID = newParent
group.SortOrder = newSortOrder
if _, err = sess.Table(group.TableName()).
ID(group.ID).
AllCols().
Update(group); err != nil {
return err
for i, gg := range siblings {
gg.SortOrder = i
if _, err = sess.Table(group.TableName()).
ID(gg.ID).
AllCols().
Update(gg); err != nil {
return err
}
}
if group.ParentGroup != nil && newParent != 0 {
group.ParentGroup = nil
if err = group.LoadParentGroup(ctx); err != nil {
@ -369,3 +477,15 @@ func MoveGroup(ctx context.Context, group *Group, newParent int64, newSortOrder
}
return nil
}
func GetOwnerByGroupID(ctx context.Context, groupID int64) (*user_model.User, error) {
e := db.GetEngine(ctx)
tableName := "repo_group"
user := new(user_model.User)
has, err := e.Join("INNER", tableName, fmt.Sprintf("`%s`.owner_id = `user`.`id`", tableName)).
Where(builder.Eq{fmt.Sprintf("`%s`.id", tableName): groupID}).Get(user)
if !has {
return nil, user_model.ErrUserNotExist{}
}
return user, err
}

View File

@ -28,41 +28,35 @@ func (groups RepoGroupList) LoadOwners(ctx context.Context) error {
return nil
}
func universalGroupPermBuilder(idStr string, userID, orgID int64) builder.Cond {
func universalGroupPermBuilder(idStr string, userID, orgID int64, includeAdmin bool) builder.Cond {
adminSubquery := builder.Select("1").
From("`user`").
Where(builder.Eq{"`user`.is_admin": true, "`user`.`id`": userID})
eqCond := builder.Eq{"`team_user`.uid": userID}
teamUserSubquery := builder.Select("`team_user`.uid").
From("team_user").
Join("INNER", "team", "`team`.id = `team_user`.team_id").
Join("LEFT", "`user` as iu", "iu.`id` = `team_user`.uid").
Where(
builder.And(
builder.Eq{"`team`.org_id": orgID},
eqCond,
builder.Or(
builder.Gte{"`team`.authorize": perm.AccessModeOwner},
)),
)
teamSubquery := builder.Select("`team`.id").From("`team`").
Join("LEFT", "team_user", "team.id = team_user.team_id").
Join("LEFT", "`user` as iu", "iu.`id` = `team_user`.uid").
Where(builder.And(
builder.Eq{"`team`.org_id": orgID},
builder.In("`team_user`.uid", teamUserSubquery),
eqCond,
builder.Gte{"`team`.authorize": perm.AccessModeOwner},
))
sq := builder.Select("`repo_group`.id").
From("`repo_group`, team_user").
From("`repo_group`").
Join("LEFT", "team", "`team`.org_id = `repo_group`.owner_id").
Where(
builder.And(
builder.Eq{"`repo_group`.owner_id": orgID},
builder.And(
builder.In("`team_user`.team_id", teamSubquery),
builder.In("`team`.id", teamSubquery),
)))
return builder.Or(builder.In(idStr, sq), builder.Exists(adminSubquery))
cond := builder.In(idStr, sq)
if includeAdmin {
cond = cond.Or(builder.Exists(adminSubquery))
}
return cond
}
// userOrgTeamGroupBuilder returns group ids where user's teams can access.
@ -104,9 +98,9 @@ func userOrgTeamUnitGroupBuilder(userID, orgID int64, unitType unit.Type) *build
func AccessibleGroupCondition(user *user_model.User, orgID int64, unitType unit.Type, minMode perm.AccessMode) builder.Cond {
cond := builder.NewCond()
if user == nil || !user.IsRestricted || user.ID <= 0 {
orgVisibilityLimit := []structs.VisibleType{structs.VisibleTypePrivate}
orgVisibilityLimit := []int{int(structs.VisibleTypePrivate)}
if user == nil || user.ID <= 0 {
orgVisibilityLimit = append(orgVisibilityLimit, structs.VisibleTypeLimited)
orgVisibilityLimit = append(orgVisibilityLimit, int(structs.VisibleTypeLimited))
}
condAnd := builder.And(
builder.NotIn("`repo_group`.owner_id", builder.Select("`user`.`id`").From("`user`").Where(
@ -118,7 +112,7 @@ func AccessibleGroupCondition(user *user_model.User, orgID int64, unitType unit.
cond = cond.Or(condAnd)
}
if user != nil {
cond = cond.Or(universalGroupPermBuilder("`repo_group`.id", user.ID, orgID))
cond = cond.Or(universalGroupPermBuilder("`repo_group`.id", user.ID, orgID, true))
cond = cond.Or(UserOrgTeamPermCond("`repo_group`.id", user.ID, orgID, minMode))
if unitType == unit.TypeInvalid {
cond = cond.Or(