diff options
Diffstat (limited to '')
-rw-r--r-- | models/quota/group.go | 401 |
1 files changed, 401 insertions, 0 deletions
diff --git a/models/quota/group.go b/models/quota/group.go new file mode 100644 index 0000000..0acb5b2 --- /dev/null +++ b/models/quota/group.go @@ -0,0 +1,401 @@ +// 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 +} |