mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-12-22 12:54:53 -05:00
3b70949651
This adds a new configuration setting: `[quota.default].TOTAL`, which will be used if no groups are configured for a particular user. The new option makes it possible to entirely skip configuring quotas via the API if all that one wants is a total size. Signed-off-by: Gergely Nagy <forgejo@gergo.csillger.hu>
401 lines
8.8 KiB
Go
401 lines
8.8 KiB
Go
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package quota
|
|
|
|
import (
|
|
"context"
|
|
|
|
"code.gitea.io/gitea/models/db"
|
|
user_model "code.gitea.io/gitea/models/user"
|
|
"code.gitea.io/gitea/modules/setting"
|
|
|
|
"xorm.io/builder"
|
|
)
|
|
|
|
type (
|
|
GroupList []*Group
|
|
Group struct {
|
|
// Name of the quota group
|
|
Name string `json:"name" xorm:"pk NOT NULL" binding:"Required"`
|
|
Rules []Rule `json:"rules" xorm:"-"`
|
|
}
|
|
)
|
|
|
|
type GroupRuleMapping struct {
|
|
ID int64 `xorm:"pk autoincr" json:"-"`
|
|
GroupName string `xorm:"index unique(qgrm_gr) not null" json:"group_name"`
|
|
RuleName string `xorm:"unique(qgrm_gr) not null" json:"rule_name"`
|
|
}
|
|
|
|
type Kind int
|
|
|
|
const (
|
|
KindUser Kind = iota
|
|
)
|
|
|
|
type GroupMapping struct {
|
|
ID int64 `xorm:"pk autoincr"`
|
|
Kind Kind `xorm:"unique(qgm_kmg) not null"`
|
|
MappedID int64 `xorm:"unique(qgm_kmg) not null"`
|
|
GroupName string `xorm:"index unique(qgm_kmg) not null"`
|
|
}
|
|
|
|
func (g *Group) TableName() string {
|
|
return "quota_group"
|
|
}
|
|
|
|
func (grm *GroupRuleMapping) TableName() string {
|
|
return "quota_group_rule_mapping"
|
|
}
|
|
|
|
func (ugm *GroupMapping) TableName() string {
|
|
return "quota_group_mapping"
|
|
}
|
|
|
|
func (g *Group) LoadRules(ctx context.Context) error {
|
|
return db.GetEngine(ctx).Select("`quota_rule`.*").
|
|
Table("quota_rule").
|
|
Join("INNER", "`quota_group_rule_mapping`", "`quota_group_rule_mapping`.rule_name = `quota_rule`.name").
|
|
Where("`quota_group_rule_mapping`.group_name = ?", g.Name).
|
|
Find(&g.Rules)
|
|
}
|
|
|
|
func (g *Group) isUserInGroup(ctx context.Context, userID int64) (bool, error) {
|
|
return db.GetEngine(ctx).
|
|
Where("kind = ? AND mapped_id = ? AND group_name = ?", KindUser, userID, g.Name).
|
|
Get(&GroupMapping{})
|
|
}
|
|
|
|
func (g *Group) AddUserByID(ctx context.Context, userID int64) error {
|
|
ctx, committer, err := db.TxContext(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer committer.Close()
|
|
|
|
exists, err := g.isUserInGroup(ctx, userID)
|
|
if err != nil {
|
|
return err
|
|
} else if exists {
|
|
return ErrUserAlreadyInGroup{GroupName: g.Name, UserID: userID}
|
|
}
|
|
|
|
_, err = db.GetEngine(ctx).Insert(&GroupMapping{
|
|
Kind: KindUser,
|
|
MappedID: userID,
|
|
GroupName: g.Name,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return committer.Commit()
|
|
}
|
|
|
|
func (g *Group) RemoveUserByID(ctx context.Context, userID int64) error {
|
|
ctx, committer, err := db.TxContext(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer committer.Close()
|
|
|
|
exists, err := g.isUserInGroup(ctx, userID)
|
|
if err != nil {
|
|
return err
|
|
} else if !exists {
|
|
return ErrUserNotInGroup{GroupName: g.Name, UserID: userID}
|
|
}
|
|
|
|
_, err = db.GetEngine(ctx).Delete(&GroupMapping{
|
|
Kind: KindUser,
|
|
MappedID: userID,
|
|
GroupName: g.Name,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return committer.Commit()
|
|
}
|
|
|
|
func (g *Group) isRuleInGroup(ctx context.Context, ruleName string) (bool, error) {
|
|
return db.GetEngine(ctx).
|
|
Where("group_name = ? AND rule_name = ?", g.Name, ruleName).
|
|
Get(&GroupRuleMapping{})
|
|
}
|
|
|
|
func (g *Group) AddRuleByName(ctx context.Context, ruleName string) error {
|
|
ctx, committer, err := db.TxContext(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer committer.Close()
|
|
|
|
exists, err := DoesRuleExist(ctx, ruleName)
|
|
if err != nil {
|
|
return err
|
|
} else if !exists {
|
|
return ErrRuleNotFound{Name: ruleName}
|
|
}
|
|
|
|
has, err := g.isRuleInGroup(ctx, ruleName)
|
|
if err != nil {
|
|
return err
|
|
} else if has {
|
|
return ErrRuleAlreadyInGroup{GroupName: g.Name, RuleName: ruleName}
|
|
}
|
|
|
|
_, err = db.GetEngine(ctx).Insert(&GroupRuleMapping{
|
|
GroupName: g.Name,
|
|
RuleName: ruleName,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return committer.Commit()
|
|
}
|
|
|
|
func (g *Group) RemoveRuleByName(ctx context.Context, ruleName string) error {
|
|
ctx, committer, err := db.TxContext(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer committer.Close()
|
|
|
|
exists, err := g.isRuleInGroup(ctx, ruleName)
|
|
if err != nil {
|
|
return err
|
|
} else if !exists {
|
|
return ErrRuleNotInGroup{GroupName: g.Name, RuleName: ruleName}
|
|
}
|
|
|
|
_, err = db.GetEngine(ctx).Delete(&GroupRuleMapping{
|
|
GroupName: g.Name,
|
|
RuleName: ruleName,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return committer.Commit()
|
|
}
|
|
|
|
var affectsMap = map[LimitSubject]LimitSubjects{
|
|
LimitSubjectSizeAll: {
|
|
LimitSubjectSizeReposAll,
|
|
LimitSubjectSizeGitLFS,
|
|
LimitSubjectSizeAssetsAll,
|
|
},
|
|
LimitSubjectSizeReposAll: {
|
|
LimitSubjectSizeReposPublic,
|
|
LimitSubjectSizeReposPrivate,
|
|
},
|
|
LimitSubjectSizeAssetsAll: {
|
|
LimitSubjectSizeAssetsAttachmentsAll,
|
|
LimitSubjectSizeAssetsArtifacts,
|
|
LimitSubjectSizeAssetsPackagesAll,
|
|
},
|
|
LimitSubjectSizeAssetsAttachmentsAll: {
|
|
LimitSubjectSizeAssetsAttachmentsIssues,
|
|
LimitSubjectSizeAssetsAttachmentsReleases,
|
|
},
|
|
}
|
|
|
|
func (g *Group) Evaluate(used Used, forSubject LimitSubject) (bool, bool) {
|
|
var found bool
|
|
for _, rule := range g.Rules {
|
|
ok, has := rule.Evaluate(used, forSubject)
|
|
if has {
|
|
found = true
|
|
if !ok {
|
|
return false, true
|
|
}
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
// If Evaluation for forSubject did not succeed, try evaluating against
|
|
// subjects below
|
|
|
|
for _, subject := range affectsMap[forSubject] {
|
|
ok, has := g.Evaluate(used, subject)
|
|
if has {
|
|
found = true
|
|
if !ok {
|
|
return false, true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true, found
|
|
}
|
|
|
|
func (gl *GroupList) Evaluate(used Used, forSubject LimitSubject) bool {
|
|
// If there are no groups, use the configured defaults:
|
|
if gl == nil || len(*gl) == 0 {
|
|
return EvaluateDefault(used, forSubject)
|
|
}
|
|
|
|
for _, group := range *gl {
|
|
ok, has := group.Evaluate(used, forSubject)
|
|
if has && ok {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func GetGroupByName(ctx context.Context, name string) (*Group, error) {
|
|
var group Group
|
|
has, err := db.GetEngine(ctx).Where("name = ?", name).Get(&group)
|
|
if has {
|
|
if err = group.LoadRules(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
return &group, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
func ListGroups(ctx context.Context) (GroupList, error) {
|
|
var groups GroupList
|
|
err := db.GetEngine(ctx).Find(&groups)
|
|
return groups, err
|
|
}
|
|
|
|
func doesGroupExist(ctx context.Context, name string) (bool, error) {
|
|
return db.GetEngine(ctx).Where("name = ?", name).Get(&Group{})
|
|
}
|
|
|
|
func CreateGroup(ctx context.Context, name string) (*Group, error) {
|
|
ctx, committer, err := db.TxContext(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer committer.Close()
|
|
|
|
exists, err := doesGroupExist(ctx, name)
|
|
if err != nil {
|
|
return nil, err
|
|
} else if exists {
|
|
return nil, ErrGroupAlreadyExists{Name: name}
|
|
}
|
|
|
|
group := Group{Name: name}
|
|
_, err = db.GetEngine(ctx).Insert(group)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &group, committer.Commit()
|
|
}
|
|
|
|
func ListUsersInGroup(ctx context.Context, name string) ([]*user_model.User, error) {
|
|
group, err := GetGroupByName(ctx, name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var users []*user_model.User
|
|
err = db.GetEngine(ctx).Select("`user`.*").
|
|
Table("user").
|
|
Join("INNER", "`quota_group_mapping`", "`quota_group_mapping`.mapped_id = `user`.id").
|
|
Where("`quota_group_mapping`.kind = ? AND `quota_group_mapping`.group_name = ?", KindUser, group.Name).
|
|
Find(&users)
|
|
return users, err
|
|
}
|
|
|
|
func DeleteGroupByName(ctx context.Context, name string) error {
|
|
ctx, committer, err := db.TxContext(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer committer.Close()
|
|
|
|
_, err = db.GetEngine(ctx).Delete(GroupMapping{
|
|
GroupName: name,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = db.GetEngine(ctx).Delete(GroupRuleMapping{
|
|
GroupName: name,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = db.GetEngine(ctx).Delete(Group{Name: name})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return committer.Commit()
|
|
}
|
|
|
|
func SetUserGroups(ctx context.Context, userID int64, groups *[]string) error {
|
|
ctx, committer, err := db.TxContext(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer committer.Close()
|
|
|
|
// First: remove the user from any groups
|
|
_, err = db.GetEngine(ctx).Where("kind = ? AND mapped_id = ?", KindUser, userID).Delete(GroupMapping{})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if groups == nil {
|
|
return nil
|
|
}
|
|
|
|
// Then add the user to each group listed
|
|
for _, groupName := range *groups {
|
|
group, err := GetGroupByName(ctx, groupName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if group == nil {
|
|
return ErrGroupNotFound{Name: groupName}
|
|
}
|
|
err = group.AddUserByID(ctx, userID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return committer.Commit()
|
|
}
|
|
|
|
func GetGroupsForUser(ctx context.Context, userID int64) (GroupList, error) {
|
|
var groups GroupList
|
|
err := db.GetEngine(ctx).
|
|
Where(builder.In("name",
|
|
builder.Select("group_name").
|
|
From("quota_group_mapping").
|
|
Where(builder.And(
|
|
builder.Eq{"kind": KindUser},
|
|
builder.Eq{"mapped_id": userID}),
|
|
))).
|
|
Find(&groups)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(groups) == 0 {
|
|
err = db.GetEngine(ctx).Where(builder.In("name", setting.Quota.DefaultGroups)).Find(&groups)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(groups) == 0 {
|
|
return nil, nil
|
|
}
|
|
}
|
|
|
|
for _, group := range groups {
|
|
err = group.LoadRules(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return groups, nil
|
|
}
|