You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

team.go 8.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. // Copyright 2018 The Gitea Authors. All rights reserved.
  2. // Copyright 2016 The Gogs Authors. All rights reserved.
  3. // Use of this source code is governed by a MIT-style
  4. // license that can be found in the LICENSE file.
  5. package organization
  6. import (
  7. "context"
  8. "fmt"
  9. "strings"
  10. "code.gitea.io/gitea/models/db"
  11. "code.gitea.io/gitea/models/perm"
  12. repo_model "code.gitea.io/gitea/models/repo"
  13. "code.gitea.io/gitea/models/unit"
  14. user_model "code.gitea.io/gitea/models/user"
  15. "code.gitea.io/gitea/modules/log"
  16. "xorm.io/builder"
  17. )
  18. // ___________
  19. // \__ ___/___ _____ _____
  20. // | |_/ __ \\__ \ / \
  21. // | |\ ___/ / __ \| Y Y \
  22. // |____| \___ >____ /__|_| /
  23. // \/ \/ \/
  24. // ErrTeamAlreadyExist represents a "TeamAlreadyExist" kind of error.
  25. type ErrTeamAlreadyExist struct {
  26. OrgID int64
  27. Name string
  28. }
  29. // IsErrTeamAlreadyExist checks if an error is a ErrTeamAlreadyExist.
  30. func IsErrTeamAlreadyExist(err error) bool {
  31. _, ok := err.(ErrTeamAlreadyExist)
  32. return ok
  33. }
  34. func (err ErrTeamAlreadyExist) Error() string {
  35. return fmt.Sprintf("team already exists [org_id: %d, name: %s]", err.OrgID, err.Name)
  36. }
  37. // ErrTeamNotExist represents a "TeamNotExist" error
  38. type ErrTeamNotExist struct {
  39. OrgID int64
  40. TeamID int64
  41. Name string
  42. }
  43. // IsErrTeamNotExist checks if an error is a ErrTeamNotExist.
  44. func IsErrTeamNotExist(err error) bool {
  45. _, ok := err.(ErrTeamNotExist)
  46. return ok
  47. }
  48. func (err ErrTeamNotExist) Error() string {
  49. return fmt.Sprintf("team does not exist [org_id %d, team_id %d, name: %s]", err.OrgID, err.TeamID, err.Name)
  50. }
  51. // OwnerTeamName return the owner team name
  52. const OwnerTeamName = "Owners"
  53. // Team represents a organization team.
  54. type Team struct {
  55. ID int64 `xorm:"pk autoincr"`
  56. OrgID int64 `xorm:"INDEX"`
  57. LowerName string
  58. Name string
  59. Description string
  60. AccessMode perm.AccessMode `xorm:"'authorize'"`
  61. Repos []*repo_model.Repository `xorm:"-"`
  62. Members []*user_model.User `xorm:"-"`
  63. NumRepos int
  64. NumMembers int
  65. Units []*TeamUnit `xorm:"-"`
  66. IncludesAllRepositories bool `xorm:"NOT NULL DEFAULT false"`
  67. CanCreateOrgRepo bool `xorm:"NOT NULL DEFAULT false"`
  68. }
  69. func init() {
  70. db.RegisterModel(new(Team))
  71. db.RegisterModel(new(TeamUser))
  72. db.RegisterModel(new(TeamRepo))
  73. db.RegisterModel(new(TeamUnit))
  74. }
  75. // SearchTeamOptions holds the search options
  76. type SearchTeamOptions struct {
  77. db.ListOptions
  78. UserID int64
  79. Keyword string
  80. OrgID int64
  81. IncludeDesc bool
  82. }
  83. // SearchTeam search for teams. Caller is responsible to check permissions.
  84. func SearchTeam(opts *SearchTeamOptions) ([]*Team, int64, error) {
  85. if opts.Page <= 0 {
  86. opts.Page = 1
  87. }
  88. if opts.PageSize == 0 {
  89. // Default limit
  90. opts.PageSize = 10
  91. }
  92. cond := builder.NewCond()
  93. if len(opts.Keyword) > 0 {
  94. lowerKeyword := strings.ToLower(opts.Keyword)
  95. var keywordCond builder.Cond = builder.Like{"lower_name", lowerKeyword}
  96. if opts.IncludeDesc {
  97. keywordCond = keywordCond.Or(builder.Like{"LOWER(description)", lowerKeyword})
  98. }
  99. cond = cond.And(keywordCond)
  100. }
  101. cond = cond.And(builder.Eq{"org_id": opts.OrgID})
  102. sess := db.GetEngine(db.DefaultContext)
  103. count, err := sess.
  104. Where(cond).
  105. Count(new(Team))
  106. if err != nil {
  107. return nil, 0, err
  108. }
  109. sess = sess.Where(cond)
  110. if opts.PageSize == -1 {
  111. opts.PageSize = int(count)
  112. } else {
  113. sess = sess.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize)
  114. }
  115. teams := make([]*Team, 0, opts.PageSize)
  116. if err = sess.
  117. OrderBy("lower_name").
  118. Find(&teams); err != nil {
  119. return nil, 0, err
  120. }
  121. return teams, count, nil
  122. }
  123. // ColorFormat provides a basic color format for a Team
  124. func (t *Team) ColorFormat(s fmt.State) {
  125. if t == nil {
  126. log.ColorFprintf(s, "%d:%s (OrgID: %d) %-v",
  127. log.NewColoredIDValue(0),
  128. "<nil>",
  129. log.NewColoredIDValue(0),
  130. 0)
  131. return
  132. }
  133. log.ColorFprintf(s, "%d:%s (OrgID: %d) %-v",
  134. log.NewColoredIDValue(t.ID),
  135. t.Name,
  136. log.NewColoredIDValue(t.OrgID),
  137. t.AccessMode)
  138. }
  139. // GetUnits return a list of available units for a team
  140. func (t *Team) GetUnits() error {
  141. return t.getUnits(db.DefaultContext)
  142. }
  143. func (t *Team) getUnits(ctx context.Context) (err error) {
  144. if t.Units != nil {
  145. return nil
  146. }
  147. t.Units, err = getUnitsByTeamID(ctx, t.ID)
  148. return err
  149. }
  150. // GetUnitNames returns the team units names
  151. func (t *Team) GetUnitNames() (res []string) {
  152. if t.AccessMode >= perm.AccessModeAdmin {
  153. return unit.AllUnitKeyNames()
  154. }
  155. for _, u := range t.Units {
  156. res = append(res, unit.Units[u.Type].NameKey)
  157. }
  158. return
  159. }
  160. // GetUnitsMap returns the team units permissions
  161. func (t *Team) GetUnitsMap() map[string]string {
  162. m := make(map[string]string)
  163. if t.AccessMode >= perm.AccessModeAdmin {
  164. for _, u := range unit.Units {
  165. m[u.NameKey] = t.AccessMode.String()
  166. }
  167. } else {
  168. for _, u := range t.Units {
  169. m[u.Unit().NameKey] = u.AccessMode.String()
  170. }
  171. }
  172. return m
  173. }
  174. // IsOwnerTeam returns true if team is owner team.
  175. func (t *Team) IsOwnerTeam() bool {
  176. return t.Name == OwnerTeamName
  177. }
  178. // IsMember returns true if given user is a member of team.
  179. func (t *Team) IsMember(userID int64) bool {
  180. isMember, err := IsTeamMember(db.DefaultContext, t.OrgID, t.ID, userID)
  181. if err != nil {
  182. log.Error("IsMember: %v", err)
  183. return false
  184. }
  185. return isMember
  186. }
  187. // GetRepositoriesCtx returns paginated repositories in team of organization.
  188. func (t *Team) GetRepositoriesCtx(ctx context.Context) (err error) {
  189. if t.Repos != nil {
  190. return nil
  191. }
  192. t.Repos, err = GetTeamRepositories(ctx, &SearchTeamRepoOptions{
  193. TeamID: t.ID,
  194. })
  195. return
  196. }
  197. // GetMembersCtx returns paginated members in team of organization.
  198. func (t *Team) GetMembersCtx(ctx context.Context) (err error) {
  199. t.Members, err = GetTeamMembers(ctx, &SearchMembersOptions{
  200. TeamID: t.ID,
  201. })
  202. return err
  203. }
  204. // UnitEnabled returns if the team has the given unit type enabled
  205. func (t *Team) UnitEnabled(tp unit.Type) bool {
  206. return t.UnitAccessMode(tp) > perm.AccessModeNone
  207. }
  208. // UnitAccessMode returns if the team has the given unit type enabled
  209. // it is called in templates, should not be replaced by `UnitAccessModeCtx(ctx ...)`
  210. func (t *Team) UnitAccessMode(tp unit.Type) perm.AccessMode {
  211. return t.UnitAccessModeCtx(db.DefaultContext, tp)
  212. }
  213. // UnitAccessModeCtx returns if the team has the given unit type enabled
  214. func (t *Team) UnitAccessModeCtx(ctx context.Context, tp unit.Type) perm.AccessMode {
  215. if err := t.getUnits(ctx); err != nil {
  216. log.Warn("Error loading team (ID: %d) units: %s", t.ID, err.Error())
  217. }
  218. for _, unit := range t.Units {
  219. if unit.Type == tp {
  220. return unit.AccessMode
  221. }
  222. }
  223. return perm.AccessModeNone
  224. }
  225. // IsUsableTeamName tests if a name could be as team name
  226. func IsUsableTeamName(name string) error {
  227. switch name {
  228. case "new":
  229. return db.ErrNameReserved{Name: name}
  230. default:
  231. return nil
  232. }
  233. }
  234. // GetTeam returns team by given team name and organization.
  235. func GetTeam(ctx context.Context, orgID int64, name string) (*Team, error) {
  236. t := &Team{
  237. OrgID: orgID,
  238. LowerName: strings.ToLower(name),
  239. }
  240. has, err := db.GetByBean(ctx, t)
  241. if err != nil {
  242. return nil, err
  243. } else if !has {
  244. return nil, ErrTeamNotExist{orgID, 0, name}
  245. }
  246. return t, nil
  247. }
  248. // GetTeamIDsByNames returns a slice of team ids corresponds to names.
  249. func GetTeamIDsByNames(orgID int64, names []string, ignoreNonExistent bool) ([]int64, error) {
  250. ids := make([]int64, 0, len(names))
  251. for _, name := range names {
  252. u, err := GetTeam(db.DefaultContext, orgID, name)
  253. if err != nil {
  254. if ignoreNonExistent {
  255. continue
  256. } else {
  257. return nil, err
  258. }
  259. }
  260. ids = append(ids, u.ID)
  261. }
  262. return ids, nil
  263. }
  264. // GetOwnerTeam returns team by given team name and organization.
  265. func GetOwnerTeam(ctx context.Context, orgID int64) (*Team, error) {
  266. return GetTeam(ctx, orgID, OwnerTeamName)
  267. }
  268. // GetTeamByID returns team by given ID.
  269. func GetTeamByID(ctx context.Context, teamID int64) (*Team, error) {
  270. t := new(Team)
  271. has, err := db.GetEngine(ctx).ID(teamID).Get(t)
  272. if err != nil {
  273. return nil, err
  274. } else if !has {
  275. return nil, ErrTeamNotExist{0, teamID, ""}
  276. }
  277. return t, nil
  278. }
  279. // GetTeamNamesByID returns team's lower name from a list of team ids.
  280. func GetTeamNamesByID(teamIDs []int64) ([]string, error) {
  281. if len(teamIDs) == 0 {
  282. return []string{}, nil
  283. }
  284. var teamNames []string
  285. err := db.GetEngine(db.DefaultContext).Table("team").
  286. Select("lower_name").
  287. In("id", teamIDs).
  288. Asc("name").
  289. Find(&teamNames)
  290. return teamNames, err
  291. }
  292. // GetRepoTeams gets the list of teams that has access to the repository
  293. func GetRepoTeams(ctx context.Context, repo *repo_model.Repository) (teams []*Team, err error) {
  294. return teams, db.GetEngine(ctx).
  295. Join("INNER", "team_repo", "team_repo.team_id = team.id").
  296. Where("team.org_id = ?", repo.OwnerID).
  297. And("team_repo.repo_id=?", repo.ID).
  298. OrderBy("CASE WHEN name LIKE '" + OwnerTeamName + "' THEN '' ELSE name END").
  299. Find(&teams)
  300. }