1// Copyright 2021 The Gitea Authors. All rights reserved. 2// Use of this source code is governed by a MIT-style 3// license that can be found in the LICENSE file. 4 5package models 6 7import ( 8 "regexp" 9 "strings" 10 11 "code.gitea.io/gitea/models/db" 12 "code.gitea.io/gitea/modules/base" 13 "code.gitea.io/gitea/modules/timeutil" 14 15 "github.com/gobwas/glob" 16) 17 18// ProtectedTag struct 19type ProtectedTag struct { 20 ID int64 `xorm:"pk autoincr"` 21 RepoID int64 22 NamePattern string 23 RegexPattern *regexp.Regexp `xorm:"-"` 24 GlobPattern glob.Glob `xorm:"-"` 25 AllowlistUserIDs []int64 `xorm:"JSON TEXT"` 26 AllowlistTeamIDs []int64 `xorm:"JSON TEXT"` 27 28 CreatedUnix timeutil.TimeStamp `xorm:"created"` 29 UpdatedUnix timeutil.TimeStamp `xorm:"updated"` 30} 31 32func init() { 33 db.RegisterModel(new(ProtectedTag)) 34} 35 36// EnsureCompiledPattern ensures the glob pattern is compiled 37func (pt *ProtectedTag) EnsureCompiledPattern() error { 38 if pt.RegexPattern != nil || pt.GlobPattern != nil { 39 return nil 40 } 41 42 var err error 43 if len(pt.NamePattern) >= 2 && strings.HasPrefix(pt.NamePattern, "/") && strings.HasSuffix(pt.NamePattern, "/") { 44 pt.RegexPattern, err = regexp.Compile(pt.NamePattern[1 : len(pt.NamePattern)-1]) 45 } else { 46 pt.GlobPattern, err = glob.Compile(pt.NamePattern) 47 } 48 return err 49} 50 51func (pt *ProtectedTag) matchString(name string) bool { 52 if pt.RegexPattern != nil { 53 return pt.RegexPattern.MatchString(name) 54 } 55 return pt.GlobPattern.Match(name) 56} 57 58// InsertProtectedTag inserts a protected tag to database 59func InsertProtectedTag(pt *ProtectedTag) error { 60 _, err := db.GetEngine(db.DefaultContext).Insert(pt) 61 return err 62} 63 64// UpdateProtectedTag updates the protected tag 65func UpdateProtectedTag(pt *ProtectedTag) error { 66 _, err := db.GetEngine(db.DefaultContext).ID(pt.ID).AllCols().Update(pt) 67 return err 68} 69 70// DeleteProtectedTag deletes a protected tag by ID 71func DeleteProtectedTag(pt *ProtectedTag) error { 72 _, err := db.GetEngine(db.DefaultContext).ID(pt.ID).Delete(&ProtectedTag{}) 73 return err 74} 75 76// IsUserAllowedModifyTag returns true if the user is allowed to modify the tag 77func IsUserAllowedModifyTag(pt *ProtectedTag, userID int64) (bool, error) { 78 if base.Int64sContains(pt.AllowlistUserIDs, userID) { 79 return true, nil 80 } 81 82 if len(pt.AllowlistTeamIDs) == 0 { 83 return false, nil 84 } 85 86 in, err := IsUserInTeams(userID, pt.AllowlistTeamIDs) 87 if err != nil { 88 return false, err 89 } 90 return in, nil 91} 92 93// GetProtectedTags gets all protected tags of the repository 94func GetProtectedTags(repoID int64) ([]*ProtectedTag, error) { 95 tags := make([]*ProtectedTag, 0) 96 return tags, db.GetEngine(db.DefaultContext).Find(&tags, &ProtectedTag{RepoID: repoID}) 97} 98 99// GetProtectedTagByID gets the protected tag with the specific id 100func GetProtectedTagByID(id int64) (*ProtectedTag, error) { 101 tag := new(ProtectedTag) 102 has, err := db.GetEngine(db.DefaultContext).ID(id).Get(tag) 103 if err != nil { 104 return nil, err 105 } 106 if !has { 107 return nil, nil 108 } 109 return tag, nil 110} 111 112// IsUserAllowedToControlTag checks if a user can control the specific tag. 113// It returns true if the tag name is not protected or the user is allowed to control it. 114func IsUserAllowedToControlTag(tags []*ProtectedTag, tagName string, userID int64) (bool, error) { 115 isAllowed := true 116 for _, tag := range tags { 117 err := tag.EnsureCompiledPattern() 118 if err != nil { 119 return false, err 120 } 121 122 if !tag.matchString(tagName) { 123 continue 124 } 125 126 isAllowed, err = IsUserAllowedModifyTag(tag, userID) 127 if err != nil { 128 return false, err 129 } 130 if isAllowed { 131 break 132 } 133 } 134 135 return isAllowed, nil 136} 137