summaryrefslogtreecommitdiffstats
path: root/models/quota/group.go
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--models/quota/group.go401
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
+}