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
6package models
7
8import (
9	"context"
10	"errors"
11	"fmt"
12	"sort"
13	"strings"
14
15	"code.gitea.io/gitea/models/db"
16	"code.gitea.io/gitea/models/perm"
17	repo_model "code.gitea.io/gitea/models/repo"
18	"code.gitea.io/gitea/models/unit"
19	user_model "code.gitea.io/gitea/models/user"
20	"code.gitea.io/gitea/modules/log"
21	"code.gitea.io/gitea/modules/setting"
22	"code.gitea.io/gitea/modules/util"
23
24	"xorm.io/builder"
25)
26
27const ownerTeamName = "Owners"
28
29// Team represents a organization team.
30type Team struct {
31	ID                      int64 `xorm:"pk autoincr"`
32	OrgID                   int64 `xorm:"INDEX"`
33	LowerName               string
34	Name                    string
35	Description             string
36	AccessMode              perm.AccessMode          `xorm:"'authorize'"`
37	Repos                   []*repo_model.Repository `xorm:"-"`
38	Members                 []*user_model.User       `xorm:"-"`
39	NumRepos                int
40	NumMembers              int
41	Units                   []*TeamUnit `xorm:"-"`
42	IncludesAllRepositories bool        `xorm:"NOT NULL DEFAULT false"`
43	CanCreateOrgRepo        bool        `xorm:"NOT NULL DEFAULT false"`
44}
45
46func init() {
47	db.RegisterModel(new(Team))
48	db.RegisterModel(new(TeamUser))
49	db.RegisterModel(new(TeamRepo))
50	db.RegisterModel(new(TeamUnit))
51}
52
53// SearchOrgTeamOptions holds the search options
54type SearchOrgTeamOptions struct {
55	db.ListOptions
56	Keyword     string
57	OrgID       int64
58	IncludeDesc bool
59}
60
61// GetUserTeamOptions holds the search options.
62type GetUserTeamOptions struct {
63	db.ListOptions
64	UserID int64
65}
66
67// SearchMembersOptions holds the search options
68type SearchMembersOptions struct {
69	db.ListOptions
70}
71
72// GetUserTeams search for org teams. Caller is responsible to check permissions.
73func GetUserTeams(opts *GetUserTeamOptions) ([]*Team, int64, error) {
74	if opts.Page <= 0 {
75		opts.Page = 1
76	}
77	if opts.PageSize == 0 {
78		// Default limit
79		opts.PageSize = 10
80	}
81
82	sess := db.GetEngine(db.DefaultContext)
83
84	sess = sess.Join("INNER", "team_user", "team_user.team_id = team.id").
85		And("team_user.uid=?", opts.UserID)
86
87	count, err := sess.
88		Count(new(Team))
89	if err != nil {
90		return nil, 0, err
91	}
92
93	if opts.PageSize == -1 {
94		opts.PageSize = int(count)
95	} else {
96		sess = sess.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize)
97	}
98
99	sess = sess.Join("INNER", "team_user", "team_user.team_id = team.id").
100		And("team_user.uid=?", opts.UserID)
101
102	teams := make([]*Team, 0, opts.PageSize)
103	if err = sess.
104		OrderBy("lower_name").
105		Find(&teams); err != nil {
106		return nil, 0, err
107	}
108
109	return teams, count, nil
110}
111
112// SearchOrgTeams search for org teams. Caller is responsible to check permissions.
113func SearchOrgTeams(opts *SearchOrgTeamOptions) ([]*Team, int64, error) {
114	if opts.Page <= 0 {
115		opts.Page = 1
116	}
117	if opts.PageSize == 0 {
118		// Default limit
119		opts.PageSize = 10
120	}
121
122	cond := builder.NewCond()
123
124	if len(opts.Keyword) > 0 {
125		lowerKeyword := strings.ToLower(opts.Keyword)
126		var keywordCond builder.Cond = builder.Like{"lower_name", lowerKeyword}
127		if opts.IncludeDesc {
128			keywordCond = keywordCond.Or(builder.Like{"LOWER(description)", lowerKeyword})
129		}
130		cond = cond.And(keywordCond)
131	}
132
133	cond = cond.And(builder.Eq{"org_id": opts.OrgID})
134
135	sess := db.GetEngine(db.DefaultContext)
136
137	count, err := sess.
138		Where(cond).
139		Count(new(Team))
140	if err != nil {
141		return nil, 0, err
142	}
143
144	sess = sess.Where(cond)
145	if opts.PageSize == -1 {
146		opts.PageSize = int(count)
147	} else {
148		sess = sess.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize)
149	}
150
151	teams := make([]*Team, 0, opts.PageSize)
152	if err = sess.
153		OrderBy("lower_name").
154		Find(&teams); err != nil {
155		return nil, 0, err
156	}
157
158	return teams, count, nil
159}
160
161// ColorFormat provides a basic color format for a Team
162func (t *Team) ColorFormat(s fmt.State) {
163	if t == nil {
164		log.ColorFprintf(s, "%d:%s (OrgID: %d) %-v",
165			log.NewColoredIDValue(0),
166			"<nil>",
167			log.NewColoredIDValue(0),
168			0)
169		return
170	}
171	log.ColorFprintf(s, "%d:%s (OrgID: %d) %-v",
172		log.NewColoredIDValue(t.ID),
173		t.Name,
174		log.NewColoredIDValue(t.OrgID),
175		t.AccessMode)
176}
177
178// GetUnits return a list of available units for a team
179func (t *Team) GetUnits() error {
180	return t.getUnits(db.GetEngine(db.DefaultContext))
181}
182
183func (t *Team) getUnits(e db.Engine) (err error) {
184	if t.Units != nil {
185		return nil
186	}
187
188	t.Units, err = getUnitsByTeamID(e, t.ID)
189	return err
190}
191
192// GetUnitNames returns the team units names
193func (t *Team) GetUnitNames() (res []string) {
194	if t.AccessMode >= perm.AccessModeAdmin {
195		return unit.AllUnitKeyNames()
196	}
197
198	for _, u := range t.Units {
199		res = append(res, unit.Units[u.Type].NameKey)
200	}
201	return
202}
203
204// GetUnitsMap returns the team units permissions
205func (t *Team) GetUnitsMap() map[string]string {
206	m := make(map[string]string)
207	if t.AccessMode >= perm.AccessModeAdmin {
208		for _, u := range unit.Units {
209			m[u.NameKey] = t.AccessMode.String()
210		}
211	} else {
212		for _, u := range t.Units {
213			m[u.Unit().NameKey] = u.AccessMode.String()
214		}
215	}
216	return m
217}
218
219// IsOwnerTeam returns true if team is owner team.
220func (t *Team) IsOwnerTeam() bool {
221	return t.Name == ownerTeamName
222}
223
224// IsMember returns true if given user is a member of team.
225func (t *Team) IsMember(userID int64) bool {
226	isMember, err := IsTeamMember(t.OrgID, t.ID, userID)
227	if err != nil {
228		log.Error("IsMember: %v", err)
229		return false
230	}
231	return isMember
232}
233
234func (t *Team) getRepositories(e db.Engine) error {
235	if t.Repos != nil {
236		return nil
237	}
238	return e.Join("INNER", "team_repo", "repository.id = team_repo.repo_id").
239		Where("team_repo.team_id=?", t.ID).
240		OrderBy("repository.name").
241		Find(&t.Repos)
242}
243
244// GetRepositories returns paginated repositories in team of organization.
245func (t *Team) GetRepositories(opts *SearchOrgTeamOptions) error {
246	if opts.Page == 0 {
247		return t.getRepositories(db.GetEngine(db.DefaultContext))
248	}
249
250	return t.getRepositories(db.GetPaginatedSession(opts))
251}
252
253func (t *Team) getMembers(e db.Engine) (err error) {
254	t.Members, err = getTeamMembers(e, t.ID)
255	return err
256}
257
258// GetMembers returns paginated members in team of organization.
259func (t *Team) GetMembers(opts *SearchMembersOptions) (err error) {
260	if opts.Page == 0 {
261		return t.getMembers(db.GetEngine(db.DefaultContext))
262	}
263
264	return t.getMembers(db.GetPaginatedSession(opts))
265}
266
267// AddMember adds new membership of the team to the organization,
268// the user will have membership to the organization automatically when needed.
269func (t *Team) AddMember(userID int64) error {
270	return AddTeamMember(t, userID)
271}
272
273// RemoveMember removes member from team of organization.
274func (t *Team) RemoveMember(userID int64) error {
275	return RemoveTeamMember(t, userID)
276}
277
278func (t *Team) hasRepository(e db.Engine, repoID int64) bool {
279	return hasTeamRepo(e, t.OrgID, t.ID, repoID)
280}
281
282// HasRepository returns true if given repository belong to team.
283func (t *Team) HasRepository(repoID int64) bool {
284	return t.hasRepository(db.GetEngine(db.DefaultContext), repoID)
285}
286
287func (t *Team) addRepository(ctx context.Context, repo *repo_model.Repository) (err error) {
288	e := db.GetEngine(ctx)
289	if err = addTeamRepo(e, t.OrgID, t.ID, repo.ID); err != nil {
290		return err
291	}
292
293	if _, err = e.Incr("num_repos").ID(t.ID).Update(new(Team)); err != nil {
294		return fmt.Errorf("update team: %v", err)
295	}
296
297	t.NumRepos++
298
299	if err = recalculateTeamAccesses(ctx, repo, 0); err != nil {
300		return fmt.Errorf("recalculateAccesses: %v", err)
301	}
302
303	// Make all team members watch this repo if enabled in global settings
304	if setting.Service.AutoWatchNewRepos {
305		if err = t.getMembers(e); err != nil {
306			return fmt.Errorf("getMembers: %v", err)
307		}
308		for _, u := range t.Members {
309			if err = repo_model.WatchRepoCtx(ctx, u.ID, repo.ID, true); err != nil {
310				return fmt.Errorf("watchRepo: %v", err)
311			}
312		}
313	}
314
315	return nil
316}
317
318// addAllRepositories adds all repositories to the team.
319// If the team already has some repositories they will be left unchanged.
320func (t *Team) addAllRepositories(ctx context.Context) error {
321	var orgRepos []repo_model.Repository
322	e := db.GetEngine(ctx)
323	if err := e.Where("owner_id = ?", t.OrgID).Find(&orgRepos); err != nil {
324		return fmt.Errorf("get org repos: %v", err)
325	}
326
327	for _, repo := range orgRepos {
328		if !t.hasRepository(e, repo.ID) {
329			if err := t.addRepository(ctx, &repo); err != nil {
330				return fmt.Errorf("addRepository: %v", err)
331			}
332		}
333	}
334
335	return nil
336}
337
338// AddAllRepositories adds all repositories to the team
339func (t *Team) AddAllRepositories() (err error) {
340	ctx, committer, err := db.TxContext()
341	if err != nil {
342		return err
343	}
344	defer committer.Close()
345
346	if err = t.addAllRepositories(ctx); err != nil {
347		return err
348	}
349
350	return committer.Commit()
351}
352
353// AddRepository adds new repository to team of organization.
354func (t *Team) AddRepository(repo *repo_model.Repository) (err error) {
355	if repo.OwnerID != t.OrgID {
356		return errors.New("Repository does not belong to organization")
357	} else if t.HasRepository(repo.ID) {
358		return nil
359	}
360
361	ctx, committer, err := db.TxContext()
362	if err != nil {
363		return err
364	}
365	defer committer.Close()
366
367	if err = t.addRepository(ctx, repo); err != nil {
368		return err
369	}
370
371	return committer.Commit()
372}
373
374// RemoveAllRepositories removes all repositories from team and recalculates access
375func (t *Team) RemoveAllRepositories() (err error) {
376	if t.IncludesAllRepositories {
377		return nil
378	}
379
380	ctx, committer, err := db.TxContext()
381	if err != nil {
382		return err
383	}
384	defer committer.Close()
385
386	if err = t.removeAllRepositories(ctx); err != nil {
387		return err
388	}
389
390	return committer.Commit()
391}
392
393// removeAllRepositories removes all repositories from team and recalculates access
394// Note: Shall not be called if team includes all repositories
395func (t *Team) removeAllRepositories(ctx context.Context) (err error) {
396	e := db.GetEngine(ctx)
397	// Delete all accesses.
398	for _, repo := range t.Repos {
399		if err := recalculateTeamAccesses(ctx, repo, t.ID); err != nil {
400			return err
401		}
402
403		// Remove watches from all users and now unaccessible repos
404		for _, user := range t.Members {
405			has, err := hasAccess(ctx, user.ID, repo)
406			if err != nil {
407				return err
408			} else if has {
409				continue
410			}
411
412			if err = repo_model.WatchRepoCtx(ctx, user.ID, repo.ID, false); err != nil {
413				return err
414			}
415
416			// Remove all IssueWatches a user has subscribed to in the repositories
417			if err = removeIssueWatchersByRepoID(e, user.ID, repo.ID); err != nil {
418				return err
419			}
420		}
421	}
422
423	// Delete team-repo
424	if _, err := e.
425		Where("team_id=?", t.ID).
426		Delete(new(TeamRepo)); err != nil {
427		return err
428	}
429
430	t.NumRepos = 0
431	if _, err = e.ID(t.ID).Cols("num_repos").Update(t); err != nil {
432		return err
433	}
434
435	return nil
436}
437
438// removeRepository removes a repository from a team and recalculates access
439// Note: Repository shall not be removed from team if it includes all repositories (unless the repository is deleted)
440func (t *Team) removeRepository(ctx context.Context, repo *repo_model.Repository, recalculate bool) (err error) {
441	e := db.GetEngine(ctx)
442	if err = removeTeamRepo(e, t.ID, repo.ID); err != nil {
443		return err
444	}
445
446	t.NumRepos--
447	if _, err = e.ID(t.ID).Cols("num_repos").Update(t); err != nil {
448		return err
449	}
450
451	// Don't need to recalculate when delete a repository from organization.
452	if recalculate {
453		if err = recalculateTeamAccesses(ctx, repo, t.ID); err != nil {
454			return err
455		}
456	}
457
458	teamUsers, err := getTeamUsersByTeamID(e, t.ID)
459	if err != nil {
460		return fmt.Errorf("getTeamUsersByTeamID: %v", err)
461	}
462	for _, teamUser := range teamUsers {
463		has, err := hasAccess(ctx, teamUser.UID, repo)
464		if err != nil {
465			return err
466		} else if has {
467			continue
468		}
469
470		if err = repo_model.WatchRepoCtx(ctx, teamUser.UID, repo.ID, false); err != nil {
471			return err
472		}
473
474		// Remove all IssueWatches a user has subscribed to in the repositories
475		if err := removeIssueWatchersByRepoID(e, teamUser.UID, repo.ID); err != nil {
476			return err
477		}
478	}
479
480	return nil
481}
482
483// RemoveRepository removes repository from team of organization.
484// If the team shall include all repositories the request is ignored.
485func (t *Team) RemoveRepository(repoID int64) error {
486	if !t.HasRepository(repoID) {
487		return nil
488	}
489
490	if t.IncludesAllRepositories {
491		return nil
492	}
493
494	repo, err := repo_model.GetRepositoryByID(repoID)
495	if err != nil {
496		return err
497	}
498
499	ctx, committer, err := db.TxContext()
500	if err != nil {
501		return err
502	}
503	defer committer.Close()
504
505	if err = t.removeRepository(ctx, repo, true); err != nil {
506		return err
507	}
508
509	return committer.Commit()
510}
511
512// UnitEnabled returns if the team has the given unit type enabled
513func (t *Team) UnitEnabled(tp unit.Type) bool {
514	return t.unitEnabled(db.GetEngine(db.DefaultContext), tp)
515}
516
517func (t *Team) unitEnabled(e db.Engine, tp unit.Type) bool {
518	return t.unitAccessMode(e, tp) > perm.AccessModeNone
519}
520
521// UnitAccessMode returns if the team has the given unit type enabled
522func (t *Team) UnitAccessMode(tp unit.Type) perm.AccessMode {
523	return t.unitAccessMode(db.GetEngine(db.DefaultContext), tp)
524}
525
526func (t *Team) unitAccessMode(e db.Engine, tp unit.Type) perm.AccessMode {
527	if err := t.getUnits(e); err != nil {
528		log.Warn("Error loading team (ID: %d) units: %s", t.ID, err.Error())
529	}
530
531	for _, unit := range t.Units {
532		if unit.Type == tp {
533			return unit.AccessMode
534		}
535	}
536	return perm.AccessModeNone
537}
538
539// IsUsableTeamName tests if a name could be as team name
540func IsUsableTeamName(name string) error {
541	switch name {
542	case "new":
543		return db.ErrNameReserved{Name: name}
544	default:
545		return nil
546	}
547}
548
549// NewTeam creates a record of new team.
550// It's caller's responsibility to assign organization ID.
551func NewTeam(t *Team) (err error) {
552	if len(t.Name) == 0 {
553		return errors.New("empty team name")
554	}
555
556	if err = IsUsableTeamName(t.Name); err != nil {
557		return err
558	}
559
560	has, err := db.GetEngine(db.DefaultContext).ID(t.OrgID).Get(new(user_model.User))
561	if err != nil {
562		return err
563	}
564	if !has {
565		return ErrOrgNotExist{t.OrgID, ""}
566	}
567
568	t.LowerName = strings.ToLower(t.Name)
569	has, err = db.GetEngine(db.DefaultContext).
570		Where("org_id=?", t.OrgID).
571		And("lower_name=?", t.LowerName).
572		Get(new(Team))
573	if err != nil {
574		return err
575	}
576	if has {
577		return ErrTeamAlreadyExist{t.OrgID, t.LowerName}
578	}
579
580	ctx, committer, err := db.TxContext()
581	if err != nil {
582		return err
583	}
584	defer committer.Close()
585
586	if err = db.Insert(ctx, t); err != nil {
587		return err
588	}
589
590	// insert units for team
591	if len(t.Units) > 0 {
592		for _, unit := range t.Units {
593			unit.TeamID = t.ID
594		}
595		if err = db.Insert(ctx, &t.Units); err != nil {
596			return err
597		}
598	}
599
600	// Add all repositories to the team if it has access to all of them.
601	if t.IncludesAllRepositories {
602		err = t.addAllRepositories(ctx)
603		if err != nil {
604			return fmt.Errorf("addAllRepositories: %v", err)
605		}
606	}
607
608	// Update organization number of teams.
609	if _, err = db.Exec(ctx, "UPDATE `user` SET num_teams=num_teams+1 WHERE id = ?", t.OrgID); err != nil {
610		return err
611	}
612	return committer.Commit()
613}
614
615func getTeam(e db.Engine, orgID int64, name string) (*Team, error) {
616	t := &Team{
617		OrgID:     orgID,
618		LowerName: strings.ToLower(name),
619	}
620	has, err := e.Get(t)
621	if err != nil {
622		return nil, err
623	} else if !has {
624		return nil, ErrTeamNotExist{orgID, 0, name}
625	}
626	return t, nil
627}
628
629// GetTeam returns team by given team name and organization.
630func GetTeam(orgID int64, name string) (*Team, error) {
631	return getTeam(db.GetEngine(db.DefaultContext), orgID, name)
632}
633
634// GetTeamIDsByNames returns a slice of team ids corresponds to names.
635func GetTeamIDsByNames(orgID int64, names []string, ignoreNonExistent bool) ([]int64, error) {
636	ids := make([]int64, 0, len(names))
637	for _, name := range names {
638		u, err := GetTeam(orgID, name)
639		if err != nil {
640			if ignoreNonExistent {
641				continue
642			} else {
643				return nil, err
644			}
645		}
646		ids = append(ids, u.ID)
647	}
648	return ids, nil
649}
650
651// getOwnerTeam returns team by given team name and organization.
652func getOwnerTeam(e db.Engine, orgID int64) (*Team, error) {
653	return getTeam(e, orgID, ownerTeamName)
654}
655
656func getTeamByID(e db.Engine, teamID int64) (*Team, error) {
657	t := new(Team)
658	has, err := e.ID(teamID).Get(t)
659	if err != nil {
660		return nil, err
661	} else if !has {
662		return nil, ErrTeamNotExist{0, teamID, ""}
663	}
664	return t, nil
665}
666
667// GetTeamByID returns team by given ID.
668func GetTeamByID(teamID int64) (*Team, error) {
669	return getTeamByID(db.GetEngine(db.DefaultContext), teamID)
670}
671
672// GetTeamNamesByID returns team's lower name from a list of team ids.
673func GetTeamNamesByID(teamIDs []int64) ([]string, error) {
674	if len(teamIDs) == 0 {
675		return []string{}, nil
676	}
677
678	var teamNames []string
679	err := db.GetEngine(db.DefaultContext).Table("team").
680		Select("lower_name").
681		In("id", teamIDs).
682		Asc("name").
683		Find(&teamNames)
684
685	return teamNames, err
686}
687
688// UpdateTeam updates information of team.
689func UpdateTeam(t *Team, authChanged, includeAllChanged bool) (err error) {
690	if len(t.Name) == 0 {
691		return errors.New("empty team name")
692	}
693
694	if len(t.Description) > 255 {
695		t.Description = t.Description[:255]
696	}
697
698	ctx, committer, err := db.TxContext()
699	if err != nil {
700		return err
701	}
702	defer committer.Close()
703	sess := db.GetEngine(ctx)
704
705	t.LowerName = strings.ToLower(t.Name)
706	has, err := sess.
707		Where("org_id=?", t.OrgID).
708		And("lower_name=?", t.LowerName).
709		And("id!=?", t.ID).
710		Get(new(Team))
711	if err != nil {
712		return err
713	} else if has {
714		return ErrTeamAlreadyExist{t.OrgID, t.LowerName}
715	}
716
717	if _, err = sess.ID(t.ID).Cols("name", "lower_name", "description",
718		"can_create_org_repo", "authorize", "includes_all_repositories").Update(t); err != nil {
719		return fmt.Errorf("update: %v", err)
720	}
721
722	// update units for team
723	if len(t.Units) > 0 {
724		for _, unit := range t.Units {
725			unit.TeamID = t.ID
726		}
727		// Delete team-unit.
728		if _, err := sess.
729			Where("team_id=?", t.ID).
730			Delete(new(TeamUnit)); err != nil {
731			return err
732		}
733		if _, err = sess.Cols("org_id", "team_id", "type", "access_mode").Insert(&t.Units); err != nil {
734			return err
735		}
736	}
737
738	// Update access for team members if needed.
739	if authChanged {
740		if err = t.getRepositories(sess); err != nil {
741			return fmt.Errorf("getRepositories: %v", err)
742		}
743
744		for _, repo := range t.Repos {
745			if err = recalculateTeamAccesses(ctx, repo, 0); err != nil {
746				return fmt.Errorf("recalculateTeamAccesses: %v", err)
747			}
748		}
749	}
750
751	// Add all repositories to the team if it has access to all of them.
752	if includeAllChanged && t.IncludesAllRepositories {
753		err = t.addAllRepositories(ctx)
754		if err != nil {
755			return fmt.Errorf("addAllRepositories: %v", err)
756		}
757	}
758
759	return committer.Commit()
760}
761
762// DeleteTeam deletes given team.
763// It's caller's responsibility to assign organization ID.
764func DeleteTeam(t *Team) error {
765	if err := t.GetRepositories(&SearchOrgTeamOptions{}); err != nil {
766		return err
767	}
768
769	ctx, committer, err := db.TxContext()
770	if err != nil {
771		return err
772	}
773	defer committer.Close()
774	sess := db.GetEngine(ctx)
775
776	if err := t.getMembers(sess); err != nil {
777		return err
778	}
779
780	// update branch protections
781	{
782		protections := make([]*ProtectedBranch, 0, 10)
783		err := sess.In("repo_id",
784			builder.Select("id").From("repository").Where(builder.Eq{"owner_id": t.OrgID})).
785			Find(&protections)
786		if err != nil {
787			return fmt.Errorf("findProtectedBranches: %v", err)
788		}
789		for _, p := range protections {
790			var matched1, matched2, matched3 bool
791			if len(p.WhitelistTeamIDs) != 0 {
792				p.WhitelistTeamIDs, matched1 = util.RemoveIDFromList(
793					p.WhitelistTeamIDs, t.ID)
794			}
795			if len(p.ApprovalsWhitelistTeamIDs) != 0 {
796				p.ApprovalsWhitelistTeamIDs, matched2 = util.RemoveIDFromList(
797					p.ApprovalsWhitelistTeamIDs, t.ID)
798			}
799			if len(p.MergeWhitelistTeamIDs) != 0 {
800				p.MergeWhitelistTeamIDs, matched3 = util.RemoveIDFromList(
801					p.MergeWhitelistTeamIDs, t.ID)
802			}
803			if matched1 || matched2 || matched3 {
804				if _, err = sess.ID(p.ID).Cols(
805					"whitelist_team_i_ds",
806					"merge_whitelist_team_i_ds",
807					"approvals_whitelist_team_i_ds",
808				).Update(p); err != nil {
809					return fmt.Errorf("updateProtectedBranches: %v", err)
810				}
811			}
812		}
813	}
814
815	if !t.IncludesAllRepositories {
816		if err := t.removeAllRepositories(ctx); err != nil {
817			return err
818		}
819	}
820
821	// Delete team-user.
822	if _, err := sess.
823		Where("org_id=?", t.OrgID).
824		Where("team_id=?", t.ID).
825		Delete(new(TeamUser)); err != nil {
826		return err
827	}
828
829	// Delete team-unit.
830	if _, err := sess.
831		Where("team_id=?", t.ID).
832		Delete(new(TeamUnit)); err != nil {
833		return err
834	}
835
836	// Delete team.
837	if _, err := sess.ID(t.ID).Delete(new(Team)); err != nil {
838		return err
839	}
840	// Update organization number of teams.
841	if _, err := sess.Exec("UPDATE `user` SET num_teams=num_teams-1 WHERE id=?", t.OrgID); err != nil {
842		return err
843	}
844
845	return committer.Commit()
846}
847
848// ___________                    ____ ___
849// \__    ___/___ _____    _____ |    |   \______ ___________
850//   |    |_/ __ \\__  \  /     \|    |   /  ___// __ \_  __ \
851//   |    |\  ___/ / __ \|  Y Y  \    |  /\___ \\  ___/|  | \/
852//   |____| \___  >____  /__|_|  /______//____  >\___  >__|
853//              \/     \/      \/             \/     \/
854
855// TeamUser represents an team-user relation.
856type TeamUser struct {
857	ID     int64 `xorm:"pk autoincr"`
858	OrgID  int64 `xorm:"INDEX"`
859	TeamID int64 `xorm:"UNIQUE(s)"`
860	UID    int64 `xorm:"UNIQUE(s)"`
861}
862
863func isTeamMember(e db.Engine, orgID, teamID, userID int64) (bool, error) {
864	return e.
865		Where("org_id=?", orgID).
866		And("team_id=?", teamID).
867		And("uid=?", userID).
868		Table("team_user").
869		Exist()
870}
871
872// IsTeamMember returns true if given user is a member of team.
873func IsTeamMember(orgID, teamID, userID int64) (bool, error) {
874	return isTeamMember(db.GetEngine(db.DefaultContext), orgID, teamID, userID)
875}
876
877func getTeamUsersByTeamID(e db.Engine, teamID int64) ([]*TeamUser, error) {
878	teamUsers := make([]*TeamUser, 0, 10)
879	return teamUsers, e.
880		Where("team_id=?", teamID).
881		Find(&teamUsers)
882}
883
884func getTeamMembers(e db.Engine, teamID int64) (_ []*user_model.User, err error) {
885	teamUsers, err := getTeamUsersByTeamID(e, teamID)
886	if err != nil {
887		return nil, fmt.Errorf("get team-users: %v", err)
888	}
889	members := make([]*user_model.User, len(teamUsers))
890	for i, teamUser := range teamUsers {
891		member, err := user_model.GetUserByIDEngine(e, teamUser.UID)
892		if err != nil {
893			return nil, fmt.Errorf("get user '%d': %v", teamUser.UID, err)
894		}
895		members[i] = member
896	}
897	sort.Slice(members, func(i, j int) bool {
898		return members[i].DisplayName() < members[j].DisplayName()
899	})
900	return members, nil
901}
902
903// GetTeamMembers returns all members in given team of organization.
904func GetTeamMembers(teamID int64) ([]*user_model.User, error) {
905	return getTeamMembers(db.GetEngine(db.DefaultContext), teamID)
906}
907
908func getUserOrgTeams(e db.Engine, orgID, userID int64) (teams []*Team, err error) {
909	return teams, e.
910		Join("INNER", "team_user", "team_user.team_id = team.id").
911		Where("team.org_id = ?", orgID).
912		And("team_user.uid=?", userID).
913		Find(&teams)
914}
915
916func getUserRepoTeams(e db.Engine, orgID, userID, repoID int64) (teams []*Team, err error) {
917	return teams, e.
918		Join("INNER", "team_user", "team_user.team_id = team.id").
919		Join("INNER", "team_repo", "team_repo.team_id = team.id").
920		Where("team.org_id = ?", orgID).
921		And("team_user.uid=?", userID).
922		And("team_repo.repo_id=?", repoID).
923		Find(&teams)
924}
925
926// GetUserOrgTeams returns all teams that user belongs to in given organization.
927func GetUserOrgTeams(orgID, userID int64) ([]*Team, error) {
928	return getUserOrgTeams(db.GetEngine(db.DefaultContext), orgID, userID)
929}
930
931// AddTeamMember adds new membership of given team to given organization,
932// the user will have membership to given organization automatically when needed.
933func AddTeamMember(team *Team, userID int64) error {
934	isAlreadyMember, err := IsTeamMember(team.OrgID, team.ID, userID)
935	if err != nil || isAlreadyMember {
936		return err
937	}
938
939	if err := AddOrgUser(team.OrgID, userID); err != nil {
940		return err
941	}
942
943	// Get team and its repositories.
944	if err := team.GetRepositories(&SearchOrgTeamOptions{}); err != nil {
945		return err
946	}
947
948	ctx, committer, err := db.TxContext()
949	if err != nil {
950		return err
951	}
952	defer committer.Close()
953
954	sess := db.GetEngine(ctx)
955
956	if err := db.Insert(ctx, &TeamUser{
957		UID:    userID,
958		OrgID:  team.OrgID,
959		TeamID: team.ID,
960	}); err != nil {
961		return err
962	} else if _, err := sess.Incr("num_members").ID(team.ID).Update(new(Team)); err != nil {
963		return err
964	}
965
966	team.NumMembers++
967
968	// Give access to team repositories.
969	for _, repo := range team.Repos {
970		if err := recalculateUserAccess(ctx, repo, userID); err != nil {
971			return err
972		}
973		if setting.Service.AutoWatchNewRepos {
974			if err = repo_model.WatchRepoCtx(ctx, userID, repo.ID, true); err != nil {
975				return err
976			}
977		}
978	}
979
980	return committer.Commit()
981}
982
983func removeTeamMember(ctx context.Context, team *Team, userID int64) error {
984	e := db.GetEngine(ctx)
985	isMember, err := isTeamMember(e, team.OrgID, team.ID, userID)
986	if err != nil || !isMember {
987		return err
988	}
989
990	// Check if the user to delete is the last member in owner team.
991	if team.IsOwnerTeam() && team.NumMembers == 1 {
992		return ErrLastOrgOwner{UID: userID}
993	}
994
995	team.NumMembers--
996
997	if err := team.getRepositories(e); err != nil {
998		return err
999	}
1000
1001	if _, err := e.Delete(&TeamUser{
1002		UID:    userID,
1003		OrgID:  team.OrgID,
1004		TeamID: team.ID,
1005	}); err != nil {
1006		return err
1007	} else if _, err = e.
1008		ID(team.ID).
1009		Cols("num_members").
1010		Update(team); err != nil {
1011		return err
1012	}
1013
1014	// Delete access to team repositories.
1015	for _, repo := range team.Repos {
1016		if err := recalculateUserAccess(ctx, repo, userID); err != nil {
1017			return err
1018		}
1019
1020		// Remove watches from now unaccessible
1021		if err := reconsiderWatches(ctx, repo, userID); err != nil {
1022			return err
1023		}
1024
1025		// Remove issue assignments from now unaccessible
1026		if err := reconsiderRepoIssuesAssignee(ctx, repo, userID); err != nil {
1027			return err
1028		}
1029	}
1030
1031	// Check if the user is a member of any team in the organization.
1032	if count, err := e.Count(&TeamUser{
1033		UID:   userID,
1034		OrgID: team.OrgID,
1035	}); err != nil {
1036		return err
1037	} else if count == 0 {
1038		return removeOrgUser(ctx, team.OrgID, userID)
1039	}
1040
1041	return nil
1042}
1043
1044// RemoveTeamMember removes member from given team of given organization.
1045func RemoveTeamMember(team *Team, userID int64) error {
1046	ctx, committer, err := db.TxContext()
1047	if err != nil {
1048		return err
1049	}
1050	defer committer.Close()
1051	if err := removeTeamMember(ctx, team, userID); err != nil {
1052		return err
1053	}
1054	return committer.Commit()
1055}
1056
1057// IsUserInTeams returns if a user in some teams
1058func IsUserInTeams(userID int64, teamIDs []int64) (bool, error) {
1059	return isUserInTeams(db.GetEngine(db.DefaultContext), userID, teamIDs)
1060}
1061
1062func isUserInTeams(e db.Engine, userID int64, teamIDs []int64) (bool, error) {
1063	return e.Where("uid=?", userID).In("team_id", teamIDs).Exist(new(TeamUser))
1064}
1065
1066// UsersInTeamsCount counts the number of users which are in userIDs and teamIDs
1067func UsersInTeamsCount(userIDs, teamIDs []int64) (int64, error) {
1068	var ids []int64
1069	if err := db.GetEngine(db.DefaultContext).In("uid", userIDs).In("team_id", teamIDs).
1070		Table("team_user").
1071		Cols("uid").GroupBy("uid").Find(&ids); err != nil {
1072		return 0, err
1073	}
1074	return int64(len(ids)), nil
1075}
1076
1077// ___________                  __________
1078// \__    ___/___ _____    _____\______   \ ____ ______   ____
1079//   |    |_/ __ \\__  \  /     \|       _// __ \\____ \ /  _ \
1080//   |    |\  ___/ / __ \|  Y Y  \    |   \  ___/|  |_> >  <_> )
1081//   |____| \___  >____  /__|_|  /____|_  /\___  >   __/ \____/
1082//              \/     \/      \/       \/     \/|__|
1083
1084// TeamRepo represents an team-repository relation.
1085type TeamRepo struct {
1086	ID     int64 `xorm:"pk autoincr"`
1087	OrgID  int64 `xorm:"INDEX"`
1088	TeamID int64 `xorm:"UNIQUE(s)"`
1089	RepoID int64 `xorm:"UNIQUE(s)"`
1090}
1091
1092func hasTeamRepo(e db.Engine, orgID, teamID, repoID int64) bool {
1093	has, _ := e.
1094		Where("org_id=?", orgID).
1095		And("team_id=?", teamID).
1096		And("repo_id=?", repoID).
1097		Get(new(TeamRepo))
1098	return has
1099}
1100
1101// HasTeamRepo returns true if given repository belongs to team.
1102func HasTeamRepo(orgID, teamID, repoID int64) bool {
1103	return hasTeamRepo(db.GetEngine(db.DefaultContext), orgID, teamID, repoID)
1104}
1105
1106func addTeamRepo(e db.Engine, orgID, teamID, repoID int64) error {
1107	_, err := e.InsertOne(&TeamRepo{
1108		OrgID:  orgID,
1109		TeamID: teamID,
1110		RepoID: repoID,
1111	})
1112	return err
1113}
1114
1115func removeTeamRepo(e db.Engine, teamID, repoID int64) error {
1116	_, err := e.Delete(&TeamRepo{
1117		TeamID: teamID,
1118		RepoID: repoID,
1119	})
1120	return err
1121}
1122
1123// GetTeamsWithAccessToRepo returns all teams in an organization that have given access level to the repository.
1124func GetTeamsWithAccessToRepo(orgID, repoID int64, mode perm.AccessMode) ([]*Team, error) {
1125	teams := make([]*Team, 0, 5)
1126	return teams, db.GetEngine(db.DefaultContext).Where("team.authorize >= ?", mode).
1127		Join("INNER", "team_repo", "team_repo.team_id = team.id").
1128		And("team_repo.org_id = ?", orgID).
1129		And("team_repo.repo_id = ?", repoID).
1130		Find(&teams)
1131}
1132
1133// ___________                    ____ ___      .__  __
1134// \__    ___/___ _____    _____ |    |   \____ |__|/  |_
1135//   |    |_/ __ \\__  \  /     \|    |   /    \|  \   __\
1136//   |    |\  ___/ / __ \|  Y Y  \    |  /   |  \  ||  |
1137//   |____| \___  >____  /__|_|  /______/|___|  /__||__|
1138//              \/     \/      \/             \/
1139
1140// TeamUnit describes all units of a repository
1141type TeamUnit struct {
1142	ID         int64     `xorm:"pk autoincr"`
1143	OrgID      int64     `xorm:"INDEX"`
1144	TeamID     int64     `xorm:"UNIQUE(s)"`
1145	Type       unit.Type `xorm:"UNIQUE(s)"`
1146	AccessMode perm.AccessMode
1147}
1148
1149// Unit returns Unit
1150func (t *TeamUnit) Unit() unit.Unit {
1151	return unit.Units[t.Type]
1152}
1153
1154func getUnitsByTeamID(e db.Engine, teamID int64) (units []*TeamUnit, err error) {
1155	return units, e.Where("team_id = ?", teamID).Find(&units)
1156}
1157
1158// UpdateTeamUnits updates a teams's units
1159func UpdateTeamUnits(team *Team, units []TeamUnit) (err error) {
1160	ctx, committer, err := db.TxContext()
1161	if err != nil {
1162		return err
1163	}
1164	defer committer.Close()
1165
1166	if _, err = db.GetEngine(ctx).Where("team_id = ?", team.ID).Delete(new(TeamUnit)); err != nil {
1167		return err
1168	}
1169
1170	if len(units) > 0 {
1171		if err = db.Insert(ctx, units); err != nil {
1172			return err
1173		}
1174	}
1175
1176	return committer.Commit()
1177}
1178