mirror of
https://github.com/go-gitea/gitea
synced 2024-11-28 17:02:59 +01:00
Move team related functions to service layer
This commit is contained in:
parent
c3dedcffa7
commit
b4eca8c776
24 changed files with 809 additions and 822 deletions
|
@ -7,529 +7,16 @@ package models
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
git_model "code.gitea.io/gitea/models/git"
|
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
"code.gitea.io/gitea/models/organization"
|
|
||||||
access_model "code.gitea.io/gitea/models/perm/access"
|
access_model "code.gitea.io/gitea/models/perm/access"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/log"
|
|
||||||
"code.gitea.io/gitea/modules/setting"
|
|
||||||
"code.gitea.io/gitea/modules/util"
|
|
||||||
|
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
)
|
)
|
||||||
|
|
||||||
func AddRepository(ctx context.Context, t *organization.Team, repo *repo_model.Repository) (err error) {
|
|
||||||
if err = organization.AddTeamRepo(ctx, t.OrgID, t.ID, repo.ID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = organization.IncrTeamRepoNum(ctx, t.ID); err != nil {
|
|
||||||
return fmt.Errorf("update team: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
t.NumRepos++
|
|
||||||
|
|
||||||
if err = access_model.RecalculateTeamAccesses(ctx, repo, 0); err != nil {
|
|
||||||
return fmt.Errorf("recalculateAccesses: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make all team members watch this repo if enabled in global settings
|
|
||||||
if setting.Service.AutoWatchNewRepos {
|
|
||||||
if err = t.LoadMembers(ctx); err != nil {
|
|
||||||
return fmt.Errorf("getMembers: %w", err)
|
|
||||||
}
|
|
||||||
for _, u := range t.Members {
|
|
||||||
if err = repo_model.WatchRepo(ctx, u, repo, true); err != nil {
|
|
||||||
return fmt.Errorf("watchRepo: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// addAllRepositories adds all repositories to the team.
|
|
||||||
// If the team already has some repositories they will be left unchanged.
|
|
||||||
func addAllRepositories(ctx context.Context, t *organization.Team) error {
|
|
||||||
orgRepos, err := organization.GetOrgRepositories(ctx, t.OrgID)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("get org repos: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, repo := range orgRepos {
|
|
||||||
if !organization.HasTeamRepo(ctx, t.OrgID, t.ID, repo.ID) {
|
|
||||||
if err := AddRepository(ctx, t, repo); err != nil {
|
|
||||||
return fmt.Errorf("AddRepository: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddAllRepositories adds all repositories to the team
|
|
||||||
func AddAllRepositories(ctx context.Context, t *organization.Team) (err error) {
|
|
||||||
ctx, committer, err := db.TxContext(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer committer.Close()
|
|
||||||
|
|
||||||
if err = addAllRepositories(ctx, t); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return committer.Commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveAllRepositories removes all repositories from team and recalculates access
|
|
||||||
func RemoveAllRepositories(ctx context.Context, t *organization.Team) (err error) {
|
|
||||||
if t.IncludesAllRepositories {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, committer, err := db.TxContext(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer committer.Close()
|
|
||||||
|
|
||||||
if err = removeAllRepositories(ctx, t); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return committer.Commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
// removeAllRepositories removes all repositories from team and recalculates access
|
|
||||||
// Note: Shall not be called if team includes all repositories
|
|
||||||
func removeAllRepositories(ctx context.Context, t *organization.Team) (err error) {
|
|
||||||
e := db.GetEngine(ctx)
|
|
||||||
// Delete all accesses.
|
|
||||||
for _, repo := range t.Repos {
|
|
||||||
if err := access_model.RecalculateTeamAccesses(ctx, repo, t.ID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove watches from all users and now unaccessible repos
|
|
||||||
for _, user := range t.Members {
|
|
||||||
has, err := access_model.HasAnyUnitAccess(ctx, user.ID, repo)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
} else if has {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = repo_model.WatchRepo(ctx, user, repo, false); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove all IssueWatches a user has subscribed to in the repositories
|
|
||||||
if err = issues_model.RemoveIssueWatchersByRepoID(ctx, user.ID, repo.ID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete team-repo
|
|
||||||
if _, err := e.
|
|
||||||
Where("team_id=?", t.ID).
|
|
||||||
Delete(new(organization.TeamRepo)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
t.NumRepos = 0
|
|
||||||
if _, err = e.ID(t.ID).Cols("num_repos").Update(t); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewTeam creates a record of new team.
|
|
||||||
// It's caller's responsibility to assign organization ID.
|
|
||||||
func NewTeam(ctx context.Context, t *organization.Team) (err error) {
|
|
||||||
if len(t.Name) == 0 {
|
|
||||||
return util.NewInvalidArgumentErrorf("empty team name")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = organization.IsUsableTeamName(t.Name); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
has, err := db.ExistByID[user_model.User](ctx, t.OrgID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !has {
|
|
||||||
return organization.ErrOrgNotExist{ID: t.OrgID}
|
|
||||||
}
|
|
||||||
|
|
||||||
t.LowerName = strings.ToLower(t.Name)
|
|
||||||
has, err = db.Exist[organization.Team](ctx, builder.Eq{
|
|
||||||
"org_id": t.OrgID,
|
|
||||||
"lower_name": t.LowerName,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if has {
|
|
||||||
return organization.ErrTeamAlreadyExist{OrgID: t.OrgID, Name: t.LowerName}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, committer, err := db.TxContext(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer committer.Close()
|
|
||||||
|
|
||||||
if err = db.Insert(ctx, t); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// insert units for team
|
|
||||||
if len(t.Units) > 0 {
|
|
||||||
for _, unit := range t.Units {
|
|
||||||
unit.TeamID = t.ID
|
|
||||||
}
|
|
||||||
if err = db.Insert(ctx, &t.Units); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add all repositories to the team if it has access to all of them.
|
|
||||||
if t.IncludesAllRepositories {
|
|
||||||
err = addAllRepositories(ctx, t)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("addAllRepositories: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update organization number of teams.
|
|
||||||
if _, err = db.Exec(ctx, "UPDATE `user` SET num_teams=num_teams+1 WHERE id = ?", t.OrgID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return committer.Commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateTeam updates information of team.
|
|
||||||
func UpdateTeam(ctx context.Context, t *organization.Team, authChanged, includeAllChanged bool) (err error) {
|
|
||||||
if len(t.Name) == 0 {
|
|
||||||
return util.NewInvalidArgumentErrorf("empty team name")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(t.Description) > 255 {
|
|
||||||
t.Description = t.Description[:255]
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, committer, err := db.TxContext(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer committer.Close()
|
|
||||||
|
|
||||||
t.LowerName = strings.ToLower(t.Name)
|
|
||||||
has, err := db.Exist[organization.Team](ctx, builder.Eq{
|
|
||||||
"org_id": t.OrgID,
|
|
||||||
"lower_name": t.LowerName,
|
|
||||||
}.And(builder.Neq{"id": t.ID}),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
} else if has {
|
|
||||||
return organization.ErrTeamAlreadyExist{OrgID: t.OrgID, Name: t.LowerName}
|
|
||||||
}
|
|
||||||
|
|
||||||
sess := db.GetEngine(ctx)
|
|
||||||
if _, err = sess.ID(t.ID).Cols("name", "lower_name", "description",
|
|
||||||
"can_create_org_repo", "authorize", "includes_all_repositories").Update(t); err != nil {
|
|
||||||
return fmt.Errorf("update: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// update units for team
|
|
||||||
if len(t.Units) > 0 {
|
|
||||||
for _, unit := range t.Units {
|
|
||||||
unit.TeamID = t.ID
|
|
||||||
}
|
|
||||||
// Delete team-unit.
|
|
||||||
if _, err := sess.
|
|
||||||
Where("team_id=?", t.ID).
|
|
||||||
Delete(new(organization.TeamUnit)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, err = sess.Cols("org_id", "team_id", "type", "access_mode").Insert(&t.Units); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update access for team members if needed.
|
|
||||||
if authChanged {
|
|
||||||
if err = t.LoadRepositories(ctx); err != nil {
|
|
||||||
return fmt.Errorf("LoadRepositories: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, repo := range t.Repos {
|
|
||||||
if err = access_model.RecalculateTeamAccesses(ctx, repo, 0); err != nil {
|
|
||||||
return fmt.Errorf("recalculateTeamAccesses: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add all repositories to the team if it has access to all of them.
|
|
||||||
if includeAllChanged && t.IncludesAllRepositories {
|
|
||||||
err = addAllRepositories(ctx, t)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("addAllRepositories: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return committer.Commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteTeam deletes given team.
|
|
||||||
// It's caller's responsibility to assign organization ID.
|
|
||||||
func DeleteTeam(ctx context.Context, t *organization.Team) error {
|
|
||||||
ctx, committer, err := db.TxContext(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer committer.Close()
|
|
||||||
|
|
||||||
if err := t.LoadRepositories(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := t.LoadMembers(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// update branch protections
|
|
||||||
{
|
|
||||||
protections := make([]*git_model.ProtectedBranch, 0, 10)
|
|
||||||
err := db.GetEngine(ctx).In("repo_id",
|
|
||||||
builder.Select("id").From("repository").Where(builder.Eq{"owner_id": t.OrgID})).
|
|
||||||
Find(&protections)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("findProtectedBranches: %w", err)
|
|
||||||
}
|
|
||||||
for _, p := range protections {
|
|
||||||
if err := git_model.RemoveTeamIDFromProtectedBranch(ctx, p, t.ID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !t.IncludesAllRepositories {
|
|
||||||
if err := removeAllRepositories(ctx, t); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := db.DeleteBeans(ctx,
|
|
||||||
&organization.Team{ID: t.ID},
|
|
||||||
&organization.TeamUser{OrgID: t.OrgID, TeamID: t.ID},
|
|
||||||
&organization.TeamUnit{TeamID: t.ID},
|
|
||||||
&organization.TeamInvite{TeamID: t.ID},
|
|
||||||
&issues_model.Review{Type: issues_model.ReviewTypeRequest, ReviewerTeamID: t.ID}, // batch delete the binding relationship between team and PR (request review from team)
|
|
||||||
); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tm := range t.Members {
|
|
||||||
if err := removeInvalidOrgUser(ctx, t.OrgID, tm); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update organization number of teams.
|
|
||||||
if _, err := db.Exec(ctx, "UPDATE `user` SET num_teams=num_teams-1 WHERE id=?", t.OrgID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return committer.Commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddTeamMember adds new membership of given team to given organization,
|
|
||||||
// the user will have membership to given organization automatically when needed.
|
|
||||||
func AddTeamMember(ctx context.Context, team *organization.Team, user *user_model.User) error {
|
|
||||||
if user_model.IsUserBlockedBy(ctx, user, team.OrgID) {
|
|
||||||
return user_model.ErrBlockedUser
|
|
||||||
}
|
|
||||||
|
|
||||||
isAlreadyMember, err := organization.IsTeamMember(ctx, team.OrgID, team.ID, user.ID)
|
|
||||||
if err != nil || isAlreadyMember {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := organization.AddOrgUser(ctx, team.OrgID, user.ID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = db.WithTx(ctx, func(ctx context.Context) error {
|
|
||||||
// check in transaction
|
|
||||||
isAlreadyMember, err = organization.IsTeamMember(ctx, team.OrgID, team.ID, user.ID)
|
|
||||||
if err != nil || isAlreadyMember {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
sess := db.GetEngine(ctx)
|
|
||||||
|
|
||||||
if err := db.Insert(ctx, &organization.TeamUser{
|
|
||||||
UID: user.ID,
|
|
||||||
OrgID: team.OrgID,
|
|
||||||
TeamID: team.ID,
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
|
||||||
} else if _, err := sess.Incr("num_members").ID(team.ID).Update(new(organization.Team)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
team.NumMembers++
|
|
||||||
|
|
||||||
// Give access to team repositories.
|
|
||||||
// update exist access if mode become bigger
|
|
||||||
subQuery := builder.Select("repo_id").From("team_repo").
|
|
||||||
Where(builder.Eq{"team_id": team.ID})
|
|
||||||
|
|
||||||
if _, err := sess.Where("user_id=?", user.ID).
|
|
||||||
In("repo_id", subQuery).
|
|
||||||
And("mode < ?", team.AccessMode).
|
|
||||||
SetExpr("mode", team.AccessMode).
|
|
||||||
Update(new(access_model.Access)); err != nil {
|
|
||||||
return fmt.Errorf("update user accesses: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// for not exist access
|
|
||||||
var repoIDs []int64
|
|
||||||
accessSubQuery := builder.Select("repo_id").From("access").Where(builder.Eq{"user_id": user.ID})
|
|
||||||
if err := sess.SQL(subQuery.And(builder.NotIn("repo_id", accessSubQuery))).Find(&repoIDs); err != nil {
|
|
||||||
return fmt.Errorf("select id accesses: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
accesses := make([]*access_model.Access, 0, 100)
|
|
||||||
for i, repoID := range repoIDs {
|
|
||||||
accesses = append(accesses, &access_model.Access{RepoID: repoID, UserID: user.ID, Mode: team.AccessMode})
|
|
||||||
if (i%100 == 0 || i == len(repoIDs)-1) && len(accesses) > 0 {
|
|
||||||
if err = db.Insert(ctx, accesses); err != nil {
|
|
||||||
return fmt.Errorf("insert new user accesses: %w", err)
|
|
||||||
}
|
|
||||||
accesses = accesses[:0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// this behaviour may spend much time so run it in a goroutine
|
|
||||||
// FIXME: Update watch repos batchly
|
|
||||||
if setting.Service.AutoWatchNewRepos {
|
|
||||||
// Get team and its repositories.
|
|
||||||
if err := team.LoadRepositories(ctx); err != nil {
|
|
||||||
log.Error("team.LoadRepositories failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: in the goroutine, it can't access the "ctx", it could only use db.DefaultContext at the moment
|
|
||||||
go func(repos []*repo_model.Repository) {
|
|
||||||
for _, repo := range repos {
|
|
||||||
if err = repo_model.WatchRepo(db.DefaultContext, user, repo, true); err != nil {
|
|
||||||
log.Error("watch repo failed: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}(team.Repos)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeTeamMember(ctx context.Context, team *organization.Team, user *user_model.User) error {
|
|
||||||
e := db.GetEngine(ctx)
|
|
||||||
isMember, err := organization.IsTeamMember(ctx, team.OrgID, team.ID, user.ID)
|
|
||||||
if err != nil || !isMember {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the user to delete is the last member in owner team.
|
|
||||||
if team.IsOwnerTeam() && team.NumMembers == 1 {
|
|
||||||
return organization.ErrLastOrgOwner{UID: user.ID}
|
|
||||||
}
|
|
||||||
|
|
||||||
team.NumMembers--
|
|
||||||
|
|
||||||
if err := team.LoadRepositories(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := e.Delete(&organization.TeamUser{
|
|
||||||
UID: user.ID,
|
|
||||||
OrgID: team.OrgID,
|
|
||||||
TeamID: team.ID,
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
|
||||||
} else if _, err = e.
|
|
||||||
ID(team.ID).
|
|
||||||
Cols("num_members").
|
|
||||||
Update(team); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete access to team repositories.
|
|
||||||
for _, repo := range team.Repos {
|
|
||||||
if err := access_model.RecalculateUserAccess(ctx, repo, user.ID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove watches from now unaccessible
|
|
||||||
if err := ReconsiderWatches(ctx, repo, user); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove issue assignments from now unaccessible
|
|
||||||
if err := ReconsiderRepoIssuesAssignee(ctx, repo, user); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return removeInvalidOrgUser(ctx, team.OrgID, user)
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeInvalidOrgUser(ctx context.Context, orgID int64, user *user_model.User) error {
|
|
||||||
// Check if the user is a member of any team in the organization.
|
|
||||||
if count, err := db.GetEngine(ctx).Count(&organization.TeamUser{
|
|
||||||
UID: user.ID,
|
|
||||||
OrgID: orgID,
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
|
||||||
} else if count == 0 {
|
|
||||||
org, err := organization.GetOrgByID(ctx, orgID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return RemoveOrgUser(ctx, org, user)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveTeamMember removes member from given team of given organization.
|
|
||||||
func RemoveTeamMember(ctx context.Context, team *organization.Team, user *user_model.User) error {
|
|
||||||
ctx, committer, err := db.TxContext(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer committer.Close()
|
|
||||||
if err := removeTeamMember(ctx, team, user); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return committer.Commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
func ReconsiderRepoIssuesAssignee(ctx context.Context, repo *repo_model.Repository, user *user_model.User) error {
|
func ReconsiderRepoIssuesAssignee(ctx context.Context, repo *repo_model.Repository, user *user_model.User) error {
|
||||||
if canAssigned, err := access_model.CanBeAssigned(ctx, user, repo, true); err != nil || canAssigned {
|
if canAssigned, err := access_model.CanBeAssigned(ctx, user, repo, true); err != nil || canAssigned {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -197,3 +197,8 @@ func TestUsersInTeamsCount(t *testing.T) {
|
||||||
test([]int64{1, 2, 3, 4, 5}, []int64{2, 5}, 2) // userid 2,4
|
test([]int64{1, 2, 3, 4, 5}, []int64{2, 5}, 2) // userid 2,4
|
||||||
test([]int64{1, 2, 3, 4, 5}, []int64{2, 3, 5}, 3) // userid 2,4,5
|
test([]int64{1, 2, 3, 4, 5}, []int64{2, 3, 5}, 3) // userid 2,4,5
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIsUsableTeamName(t *testing.T) {
|
||||||
|
assert.NoError(t, organization.IsUsableTeamName("usable"))
|
||||||
|
assert.True(t, db.IsErrNameReserved(organization.IsUsableTeamName("new")))
|
||||||
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models"
|
|
||||||
"code.gitea.io/gitea/models/organization"
|
"code.gitea.io/gitea/models/organization"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
|
@ -15,6 +14,7 @@ import (
|
||||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
"code.gitea.io/gitea/services/convert"
|
"code.gitea.io/gitea/services/convert"
|
||||||
|
org_service "code.gitea.io/gitea/services/org"
|
||||||
)
|
)
|
||||||
|
|
||||||
// listMembers list an organization's members
|
// listMembers list an organization's members
|
||||||
|
@ -322,7 +322,7 @@ func DeleteMember(ctx *context.APIContext) {
|
||||||
if ctx.Written() {
|
if ctx.Written() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := models.RemoveOrgUser(ctx, ctx.Org.Organization, member); err != nil {
|
if err := org_service.RemoveOrgUser(ctx, ctx.Org.Organization, member); err != nil {
|
||||||
ctx.Error(http.StatusInternalServerError, "RemoveOrgUser", err)
|
ctx.Error(http.StatusInternalServerError, "RemoveOrgUser", err)
|
||||||
}
|
}
|
||||||
ctx.Status(http.StatusNoContent)
|
ctx.Status(http.StatusNoContent)
|
||||||
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models"
|
|
||||||
activities_model "code.gitea.io/gitea/models/activities"
|
activities_model "code.gitea.io/gitea/models/activities"
|
||||||
"code.gitea.io/gitea/models/organization"
|
"code.gitea.io/gitea/models/organization"
|
||||||
"code.gitea.io/gitea/models/perm"
|
"code.gitea.io/gitea/models/perm"
|
||||||
|
@ -240,7 +239,7 @@ func CreateTeam(ctx *context.APIContext) {
|
||||||
attachAdminTeamUnits(team)
|
attachAdminTeamUnits(team)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := models.NewTeam(ctx, team); err != nil {
|
if err := org_service.NewTeam(ctx, team); err != nil {
|
||||||
if organization.IsErrTeamAlreadyExist(err) {
|
if organization.IsErrTeamAlreadyExist(err) {
|
||||||
ctx.Error(http.StatusUnprocessableEntity, "", err)
|
ctx.Error(http.StatusUnprocessableEntity, "", err)
|
||||||
} else {
|
} else {
|
||||||
|
@ -331,7 +330,7 @@ func EditTeam(ctx *context.APIContext) {
|
||||||
attachAdminTeamUnits(team)
|
attachAdminTeamUnits(team)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := models.UpdateTeam(ctx, team, isAuthChanged, isIncludeAllChanged); err != nil {
|
if err := org_service.UpdateTeam(ctx, team, isAuthChanged, isIncludeAllChanged); err != nil {
|
||||||
ctx.Error(http.StatusInternalServerError, "EditTeam", err)
|
ctx.Error(http.StatusInternalServerError, "EditTeam", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -362,7 +361,7 @@ func DeleteTeam(ctx *context.APIContext) {
|
||||||
// "404":
|
// "404":
|
||||||
// "$ref": "#/responses/notFound"
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
if err := models.DeleteTeam(ctx, ctx.Org.Team); err != nil {
|
if err := org_service.DeleteTeam(ctx, ctx.Org.Team); err != nil {
|
||||||
ctx.Error(http.StatusInternalServerError, "DeleteTeam", err)
|
ctx.Error(http.StatusInternalServerError, "DeleteTeam", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -496,7 +495,7 @@ func AddTeamMember(ctx *context.APIContext) {
|
||||||
if ctx.Written() {
|
if ctx.Written() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := models.AddTeamMember(ctx, ctx.Org.Team, u); err != nil {
|
if err := org_service.AddTeamMember(ctx, ctx.Org.Team, u); err != nil {
|
||||||
if errors.Is(err, user_model.ErrBlockedUser) {
|
if errors.Is(err, user_model.ErrBlockedUser) {
|
||||||
ctx.Error(http.StatusForbidden, "AddTeamMember", err)
|
ctx.Error(http.StatusForbidden, "AddTeamMember", err)
|
||||||
} else {
|
} else {
|
||||||
|
@ -537,7 +536,7 @@ func RemoveTeamMember(ctx *context.APIContext) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := models.RemoveTeamMember(ctx, ctx.Org.Team, u); err != nil {
|
if err := org_service.RemoveTeamMember(ctx, ctx.Org.Team, u); err != nil {
|
||||||
ctx.Error(http.StatusInternalServerError, "RemoveTeamMember", err)
|
ctx.Error(http.StatusInternalServerError, "RemoveTeamMember", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -700,7 +699,7 @@ func AddTeamRepository(ctx *context.APIContext) {
|
||||||
ctx.Error(http.StatusForbidden, "", "Must have admin-level access to the repository")
|
ctx.Error(http.StatusForbidden, "", "Must have admin-level access to the repository")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := org_service.TeamAddRepository(ctx, ctx.Org.Team, repo); err != nil {
|
if err := repo_service.TeamAddRepository(ctx, ctx.Org.Team, repo); err != nil {
|
||||||
ctx.Error(http.StatusInternalServerError, "TeamAddRepository", err)
|
ctx.Error(http.StatusInternalServerError, "TeamAddRepository", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"code.gitea.io/gitea/models/organization"
|
"code.gitea.io/gitea/models/organization"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
"code.gitea.io/gitea/services/convert"
|
"code.gitea.io/gitea/services/convert"
|
||||||
org_service "code.gitea.io/gitea/services/org"
|
|
||||||
repo_service "code.gitea.io/gitea/services/repository"
|
repo_service "code.gitea.io/gitea/services/repository"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -205,7 +204,7 @@ func changeRepoTeam(ctx *context.APIContext, add bool) {
|
||||||
ctx.Error(http.StatusUnprocessableEntity, "alreadyAdded", fmt.Errorf("team '%s' is already added to repo", team.Name))
|
ctx.Error(http.StatusUnprocessableEntity, "alreadyAdded", fmt.Errorf("team '%s' is already added to repo", team.Name))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = org_service.TeamAddRepository(ctx, team, ctx.Repo.Repository)
|
err = repo_service.TeamAddRepository(ctx, team, ctx.Repo.Repository)
|
||||||
} else {
|
} else {
|
||||||
if !repoHasTeam {
|
if !repoHasTeam {
|
||||||
ctx.Error(http.StatusUnprocessableEntity, "notAdded", fmt.Errorf("team '%s' was not added to repo", team.Name))
|
ctx.Error(http.StatusUnprocessableEntity, "notAdded", fmt.Errorf("team '%s' was not added to repo", team.Name))
|
||||||
|
|
|
@ -7,7 +7,6 @@ package org
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models"
|
|
||||||
"code.gitea.io/gitea/models/organization"
|
"code.gitea.io/gitea/models/organization"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
|
@ -15,6 +14,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
shared_user "code.gitea.io/gitea/routers/web/shared/user"
|
shared_user "code.gitea.io/gitea/routers/web/shared/user"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
|
org_service "code.gitea.io/gitea/services/org"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -108,14 +108,14 @@ func MembersAction(ctx *context.Context) {
|
||||||
ctx.Error(http.StatusNotFound)
|
ctx.Error(http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = models.RemoveOrgUser(ctx, org, member)
|
err = org_service.RemoveOrgUser(ctx, org, member)
|
||||||
if organization.IsErrLastOrgOwner(err) {
|
if organization.IsErrLastOrgOwner(err) {
|
||||||
ctx.Flash.Error(ctx.Tr("form.last_org_owner"))
|
ctx.Flash.Error(ctx.Tr("form.last_org_owner"))
|
||||||
ctx.JSONRedirect(ctx.Org.OrgLink + "/members")
|
ctx.JSONRedirect(ctx.Org.OrgLink + "/members")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "leave":
|
case "leave":
|
||||||
err = models.RemoveOrgUser(ctx, org, ctx.Doer)
|
err = org_service.RemoveOrgUser(ctx, org, ctx.Doer)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
ctx.Flash.Success(ctx.Tr("form.organization_leave_success", org.DisplayName()))
|
ctx.Flash.Success(ctx.Tr("form.organization_leave_success", org.DisplayName()))
|
||||||
ctx.JSON(http.StatusOK, map[string]any{
|
ctx.JSON(http.StatusOK, map[string]any{
|
||||||
|
|
|
@ -13,7 +13,6 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models"
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
org_model "code.gitea.io/gitea/models/organization"
|
org_model "code.gitea.io/gitea/models/organization"
|
||||||
"code.gitea.io/gitea/models/perm"
|
"code.gitea.io/gitea/models/perm"
|
||||||
|
@ -78,9 +77,9 @@ func TeamsAction(ctx *context.Context) {
|
||||||
ctx.Error(http.StatusNotFound)
|
ctx.Error(http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = models.AddTeamMember(ctx, ctx.Org.Team, ctx.Doer)
|
err = org_service.AddTeamMember(ctx, ctx.Org.Team, ctx.Doer)
|
||||||
case "leave":
|
case "leave":
|
||||||
err = models.RemoveTeamMember(ctx, ctx.Org.Team, ctx.Doer)
|
err = org_service.RemoveTeamMember(ctx, ctx.Org.Team, ctx.Doer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if org_model.IsErrLastOrgOwner(err) {
|
if org_model.IsErrLastOrgOwner(err) {
|
||||||
ctx.Flash.Error(ctx.Tr("form.last_org_owner"))
|
ctx.Flash.Error(ctx.Tr("form.last_org_owner"))
|
||||||
|
@ -107,7 +106,7 @@ func TeamsAction(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = models.RemoveTeamMember(ctx, ctx.Org.Team, user)
|
err = org_service.RemoveTeamMember(ctx, ctx.Org.Team, user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if org_model.IsErrLastOrgOwner(err) {
|
if org_model.IsErrLastOrgOwner(err) {
|
||||||
ctx.Flash.Error(ctx.Tr("form.last_org_owner"))
|
ctx.Flash.Error(ctx.Tr("form.last_org_owner"))
|
||||||
|
@ -162,7 +161,7 @@ func TeamsAction(ctx *context.Context) {
|
||||||
if ctx.Org.Team.IsMember(ctx, u.ID) {
|
if ctx.Org.Team.IsMember(ctx, u.ID) {
|
||||||
ctx.Flash.Error(ctx.Tr("org.teams.add_duplicate_users"))
|
ctx.Flash.Error(ctx.Tr("org.teams.add_duplicate_users"))
|
||||||
} else {
|
} else {
|
||||||
err = models.AddTeamMember(ctx, ctx.Org.Team, u)
|
err = org_service.AddTeamMember(ctx, ctx.Org.Team, u)
|
||||||
}
|
}
|
||||||
|
|
||||||
page = "team"
|
page = "team"
|
||||||
|
@ -249,13 +248,13 @@ func TeamsRepoAction(ctx *context.Context) {
|
||||||
ctx.ServerError("GetRepositoryByName", err)
|
ctx.ServerError("GetRepositoryByName", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = org_service.TeamAddRepository(ctx, ctx.Org.Team, repo)
|
err = repo_service.TeamAddRepository(ctx, ctx.Org.Team, repo)
|
||||||
case "remove":
|
case "remove":
|
||||||
err = repo_service.RemoveRepositoryFromTeam(ctx, ctx.Org.Team, ctx.FormInt64("repoid"))
|
err = repo_service.RemoveRepositoryFromTeam(ctx, ctx.Org.Team, ctx.FormInt64("repoid"))
|
||||||
case "addall":
|
case "addall":
|
||||||
err = models.AddAllRepositories(ctx, ctx.Org.Team)
|
err = repo_service.AddAllRepositoriesToTeam(ctx, ctx.Org.Team)
|
||||||
case "removeall":
|
case "removeall":
|
||||||
err = models.RemoveAllRepositories(ctx, ctx.Org.Team)
|
err = repo_service.RemoveAllRepositoriesFromTeam(ctx, ctx.Org.Team)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -358,7 +357,7 @@ func NewTeamPost(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := models.NewTeam(ctx, t); err != nil {
|
if err := org_service.NewTeam(ctx, t); err != nil {
|
||||||
ctx.Data["Err_TeamName"] = true
|
ctx.Data["Err_TeamName"] = true
|
||||||
switch {
|
switch {
|
||||||
case org_model.IsErrTeamAlreadyExist(err):
|
case org_model.IsErrTeamAlreadyExist(err):
|
||||||
|
@ -536,7 +535,7 @@ func EditTeamPost(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := models.UpdateTeam(ctx, t, isAuthChanged, isIncludeAllChanged); err != nil {
|
if err := org_service.UpdateTeam(ctx, t, isAuthChanged, isIncludeAllChanged); err != nil {
|
||||||
ctx.Data["Err_TeamName"] = true
|
ctx.Data["Err_TeamName"] = true
|
||||||
switch {
|
switch {
|
||||||
case org_model.IsErrTeamAlreadyExist(err):
|
case org_model.IsErrTeamAlreadyExist(err):
|
||||||
|
@ -551,7 +550,7 @@ func EditTeamPost(ctx *context.Context) {
|
||||||
|
|
||||||
// DeleteTeam response for the delete team request
|
// DeleteTeam response for the delete team request
|
||||||
func DeleteTeam(ctx *context.Context) {
|
func DeleteTeam(ctx *context.Context) {
|
||||||
if err := models.DeleteTeam(ctx, ctx.Org.Team); err != nil {
|
if err := org_service.DeleteTeam(ctx, ctx.Org.Team); err != nil {
|
||||||
ctx.Flash.Error("DeleteTeam: " + err.Error())
|
ctx.Flash.Error("DeleteTeam: " + err.Error())
|
||||||
} else {
|
} else {
|
||||||
ctx.Flash.Success(ctx.Tr("org.teams.delete_team_success"))
|
ctx.Flash.Success(ctx.Tr("org.teams.delete_team_success"))
|
||||||
|
@ -593,7 +592,7 @@ func TeamInvitePost(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := models.AddTeamMember(ctx, team, ctx.Doer); err != nil {
|
if err := org_service.AddTeamMember(ctx, team, ctx.Doer); err != nil {
|
||||||
ctx.ServerError("AddTeamMember", err)
|
ctx.ServerError("AddTeamMember", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,6 @@ import (
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
"code.gitea.io/gitea/services/mailer"
|
"code.gitea.io/gitea/services/mailer"
|
||||||
org_service "code.gitea.io/gitea/services/org"
|
|
||||||
repo_service "code.gitea.io/gitea/services/repository"
|
repo_service "code.gitea.io/gitea/services/repository"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -185,7 +184,7 @@ func AddTeamPost(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = org_service.TeamAddRepository(ctx, team, ctx.Repo.Repository); err != nil {
|
if err = repo_service.TeamAddRepository(ctx, team, ctx.Repo.Repository); err != nil {
|
||||||
ctx.ServerError("TeamAddRepository", err)
|
ctx.ServerError("TeamAddRepository", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,11 +7,11 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models"
|
|
||||||
"code.gitea.io/gitea/models/organization"
|
"code.gitea.io/gitea/models/organization"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/container"
|
"code.gitea.io/gitea/modules/container"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
org_service "code.gitea.io/gitea/services/org"
|
||||||
)
|
)
|
||||||
|
|
||||||
type syncType int
|
type syncType int
|
||||||
|
@ -100,12 +100,12 @@ func syncGroupsToTeamsCached(ctx context.Context, user *user_model.User, orgTeam
|
||||||
}
|
}
|
||||||
|
|
||||||
if action == syncAdd && !isMember {
|
if action == syncAdd && !isMember {
|
||||||
if err := models.AddTeamMember(ctx, team, user); err != nil {
|
if err := org_service.AddTeamMember(ctx, team, user); err != nil {
|
||||||
log.Error("group sync: Could not add user to team: %v", err)
|
log.Error("group sync: Could not add user to team: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else if action == syncRemove && isMember {
|
} else if action == syncRemove && isMember {
|
||||||
if err := models.RemoveTeamMember(ctx, team, user); err != nil {
|
if err := org_service.RemoveTeamMember(ctx, team, user); err != nil {
|
||||||
log.Error("group sync: Could not remove user from team: %v", err)
|
log.Error("group sync: Could not remove user from team: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,11 +6,11 @@ package doctor
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models"
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
org_model "code.gitea.io/gitea/models/organization"
|
org_model "code.gitea.io/gitea/models/organization"
|
||||||
"code.gitea.io/gitea/models/perm"
|
"code.gitea.io/gitea/models/perm"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
org_service "code.gitea.io/gitea/services/org"
|
||||||
|
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
)
|
)
|
||||||
|
@ -29,7 +29,7 @@ func fixOwnerTeamCreateOrgRepo(ctx context.Context, logger log.Logger, autofix b
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return models.UpdateTeam(ctx, team, false, false)
|
return org_service.UpdateTeam(ctx, team, false, false)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package org
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/models"
|
|
||||||
"code.gitea.io/gitea/models/db"
|
|
||||||
"code.gitea.io/gitea/models/organization"
|
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TeamAddRepository adds new repository to team of organization.
|
|
||||||
func TeamAddRepository(ctx context.Context, t *organization.Team, repo *repo_model.Repository) (err error) {
|
|
||||||
if repo.OwnerID != t.OrgID {
|
|
||||||
return errors.New("repository does not belong to organization")
|
|
||||||
} else if organization.HasTeamRepo(ctx, t.OrgID, t.ID, repo.ID) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
|
||||||
return models.AddRepository(ctx, t, repo)
|
|
||||||
})
|
|
||||||
}
|
|
402
services/org/team.go
Normal file
402
services/org/team.go
Normal file
|
@ -0,0 +1,402 @@
|
||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package org
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models"
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
git_model "code.gitea.io/gitea/models/git"
|
||||||
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
|
"code.gitea.io/gitea/models/organization"
|
||||||
|
access_model "code.gitea.io/gitea/models/perm/access"
|
||||||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
repo_service "code.gitea.io/gitea/services/repository"
|
||||||
|
|
||||||
|
"xorm.io/builder"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewTeam creates a record of new team.
|
||||||
|
// It's caller's responsibility to assign organization ID.
|
||||||
|
func NewTeam(ctx context.Context, t *organization.Team) (err error) {
|
||||||
|
if len(t.Name) == 0 {
|
||||||
|
return util.NewInvalidArgumentErrorf("empty team name")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = organization.IsUsableTeamName(t.Name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
has, err := db.ExistByID[user_model.User](ctx, t.OrgID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !has {
|
||||||
|
return organization.ErrOrgNotExist{ID: t.OrgID}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.LowerName = strings.ToLower(t.Name)
|
||||||
|
has, err = db.Exist[organization.Team](ctx, builder.Eq{
|
||||||
|
"org_id": t.OrgID,
|
||||||
|
"lower_name": t.LowerName,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if has {
|
||||||
|
return organization.ErrTeamAlreadyExist{OrgID: t.OrgID, Name: t.LowerName}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, committer, err := db.TxContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer committer.Close()
|
||||||
|
|
||||||
|
if err = db.Insert(ctx, t); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert units for team
|
||||||
|
if len(t.Units) > 0 {
|
||||||
|
for _, unit := range t.Units {
|
||||||
|
unit.TeamID = t.ID
|
||||||
|
}
|
||||||
|
if err = db.Insert(ctx, &t.Units); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all repositories to the team if it has access to all of them.
|
||||||
|
if t.IncludesAllRepositories {
|
||||||
|
err = repo_service.AddAllRepositoriesToTeam(ctx, t)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("addAllRepositories: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update organization number of teams.
|
||||||
|
if _, err = db.Exec(ctx, "UPDATE `user` SET num_teams=num_teams+1 WHERE id = ?", t.OrgID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return committer.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateTeam updates information of team.
|
||||||
|
func UpdateTeam(ctx context.Context, t *organization.Team, authChanged, includeAllChanged bool) (err error) {
|
||||||
|
if len(t.Name) == 0 {
|
||||||
|
return util.NewInvalidArgumentErrorf("empty team name")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(t.Description) > 255 {
|
||||||
|
t.Description = t.Description[:255]
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, committer, err := db.TxContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer committer.Close()
|
||||||
|
|
||||||
|
t.LowerName = strings.ToLower(t.Name)
|
||||||
|
has, err := db.Exist[organization.Team](ctx, builder.Eq{
|
||||||
|
"org_id": t.OrgID,
|
||||||
|
"lower_name": t.LowerName,
|
||||||
|
}.And(builder.Neq{"id": t.ID}),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if has {
|
||||||
|
return organization.ErrTeamAlreadyExist{OrgID: t.OrgID, Name: t.LowerName}
|
||||||
|
}
|
||||||
|
|
||||||
|
sess := db.GetEngine(ctx)
|
||||||
|
if _, err = sess.ID(t.ID).Cols("name", "lower_name", "description",
|
||||||
|
"can_create_org_repo", "authorize", "includes_all_repositories").Update(t); err != nil {
|
||||||
|
return fmt.Errorf("update: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// update units for team
|
||||||
|
if len(t.Units) > 0 {
|
||||||
|
for _, unit := range t.Units {
|
||||||
|
unit.TeamID = t.ID
|
||||||
|
}
|
||||||
|
// Delete team-unit.
|
||||||
|
if _, err := sess.
|
||||||
|
Where("team_id=?", t.ID).
|
||||||
|
Delete(new(organization.TeamUnit)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err = sess.Cols("org_id", "team_id", "type", "access_mode").Insert(&t.Units); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update access for team members if needed.
|
||||||
|
if authChanged {
|
||||||
|
if err = t.LoadRepositories(ctx); err != nil {
|
||||||
|
return fmt.Errorf("LoadRepositories: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, repo := range t.Repos {
|
||||||
|
if err = access_model.RecalculateTeamAccesses(ctx, repo, 0); err != nil {
|
||||||
|
return fmt.Errorf("recalculateTeamAccesses: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add all repositories to the team if it has access to all of them.
|
||||||
|
if includeAllChanged && t.IncludesAllRepositories {
|
||||||
|
err = repo_service.AddAllRepositoriesToTeam(ctx, t)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("addAllRepositories: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return committer.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddTeamMember adds new membership of given team to given organization,
|
||||||
|
// the user will have membership to given organization automatically when needed.
|
||||||
|
func AddTeamMember(ctx context.Context, team *organization.Team, user *user_model.User) error {
|
||||||
|
if user_model.IsUserBlockedBy(ctx, user, team.OrgID) {
|
||||||
|
return user_model.ErrBlockedUser
|
||||||
|
}
|
||||||
|
|
||||||
|
isAlreadyMember, err := organization.IsTeamMember(ctx, team.OrgID, team.ID, user.ID)
|
||||||
|
if err != nil || isAlreadyMember {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := organization.AddOrgUser(ctx, team.OrgID, user.ID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.WithTx(ctx, func(ctx context.Context) error {
|
||||||
|
// check in transaction
|
||||||
|
isAlreadyMember, err = organization.IsTeamMember(ctx, team.OrgID, team.ID, user.ID)
|
||||||
|
if err != nil || isAlreadyMember {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
sess := db.GetEngine(ctx)
|
||||||
|
|
||||||
|
if err := db.Insert(ctx, &organization.TeamUser{
|
||||||
|
UID: user.ID,
|
||||||
|
OrgID: team.OrgID,
|
||||||
|
TeamID: team.ID,
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
} else if _, err := sess.Incr("num_members").ID(team.ID).Update(new(organization.Team)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
team.NumMembers++
|
||||||
|
|
||||||
|
// Give access to team repositories.
|
||||||
|
// update exist access if mode become bigger
|
||||||
|
subQuery := builder.Select("repo_id").From("team_repo").
|
||||||
|
Where(builder.Eq{"team_id": team.ID})
|
||||||
|
|
||||||
|
if _, err := sess.Where("user_id=?", user.ID).
|
||||||
|
In("repo_id", subQuery).
|
||||||
|
And("mode < ?", team.AccessMode).
|
||||||
|
SetExpr("mode", team.AccessMode).
|
||||||
|
Update(new(access_model.Access)); err != nil {
|
||||||
|
return fmt.Errorf("update user accesses: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// for not exist access
|
||||||
|
var repoIDs []int64
|
||||||
|
accessSubQuery := builder.Select("repo_id").From("access").Where(builder.Eq{"user_id": user.ID})
|
||||||
|
if err := sess.SQL(subQuery.And(builder.NotIn("repo_id", accessSubQuery))).Find(&repoIDs); err != nil {
|
||||||
|
return fmt.Errorf("select id accesses: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
accesses := make([]*access_model.Access, 0, 100)
|
||||||
|
for i, repoID := range repoIDs {
|
||||||
|
accesses = append(accesses, &access_model.Access{RepoID: repoID, UserID: user.ID, Mode: team.AccessMode})
|
||||||
|
if (i%100 == 0 || i == len(repoIDs)-1) && len(accesses) > 0 {
|
||||||
|
if err = db.Insert(ctx, accesses); err != nil {
|
||||||
|
return fmt.Errorf("insert new user accesses: %w", err)
|
||||||
|
}
|
||||||
|
accesses = accesses[:0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// this behaviour may spend much time so run it in a goroutine
|
||||||
|
// FIXME: Update watch repos batchly
|
||||||
|
if setting.Service.AutoWatchNewRepos {
|
||||||
|
// Get team and its repositories.
|
||||||
|
if err := team.LoadRepositories(ctx); err != nil {
|
||||||
|
log.Error("team.LoadRepositories failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: in the goroutine, it can't access the "ctx", it could only use db.DefaultContext at the moment
|
||||||
|
go func(repos []*repo_model.Repository) {
|
||||||
|
for _, repo := range repos {
|
||||||
|
if err = repo_model.WatchRepo(db.DefaultContext, user, repo, true); err != nil {
|
||||||
|
log.Error("watch repo failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(team.Repos)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteTeam deletes given team.
|
||||||
|
// It's caller's responsibility to assign organization ID.
|
||||||
|
func DeleteTeam(ctx context.Context, t *organization.Team) error {
|
||||||
|
ctx, committer, err := db.TxContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer committer.Close()
|
||||||
|
|
||||||
|
if err := t.LoadRepositories(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := t.LoadMembers(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// update branch protections
|
||||||
|
{
|
||||||
|
protections := make([]*git_model.ProtectedBranch, 0, 10)
|
||||||
|
err := db.GetEngine(ctx).In("repo_id",
|
||||||
|
builder.Select("id").From("repository").Where(builder.Eq{"owner_id": t.OrgID})).
|
||||||
|
Find(&protections)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("findProtectedBranches: %w", err)
|
||||||
|
}
|
||||||
|
for _, p := range protections {
|
||||||
|
if err := git_model.RemoveTeamIDFromProtectedBranch(ctx, p, t.ID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := repo_service.RemoveAllRepositoriesFromTeam(ctx, t); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.DeleteBeans(ctx,
|
||||||
|
&organization.Team{ID: t.ID},
|
||||||
|
&organization.TeamUser{OrgID: t.OrgID, TeamID: t.ID},
|
||||||
|
&organization.TeamUnit{TeamID: t.ID},
|
||||||
|
&organization.TeamInvite{TeamID: t.ID},
|
||||||
|
&issues_model.Review{Type: issues_model.ReviewTypeRequest, ReviewerTeamID: t.ID}, // batch delete the binding relationship between team and PR (request review from team)
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tm := range t.Members {
|
||||||
|
if err := removeInvalidOrgUser(ctx, t.OrgID, tm); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update organization number of teams.
|
||||||
|
if _, err := db.Exec(ctx, "UPDATE `user` SET num_teams=num_teams-1 WHERE id=?", t.OrgID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return committer.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveTeamMember removes member from given team of given organization.
|
||||||
|
func RemoveTeamMember(ctx context.Context, team *organization.Team, user *user_model.User) error {
|
||||||
|
ctx, committer, err := db.TxContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer committer.Close()
|
||||||
|
if err := removeTeamMember(ctx, team, user); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return committer.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeTeamMember(ctx context.Context, team *organization.Team, user *user_model.User) error {
|
||||||
|
e := db.GetEngine(ctx)
|
||||||
|
isMember, err := organization.IsTeamMember(ctx, team.OrgID, team.ID, user.ID)
|
||||||
|
if err != nil || !isMember {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the user to delete is the last member in owner team.
|
||||||
|
if team.IsOwnerTeam() && team.NumMembers == 1 {
|
||||||
|
return organization.ErrLastOrgOwner{UID: user.ID}
|
||||||
|
}
|
||||||
|
|
||||||
|
team.NumMembers--
|
||||||
|
|
||||||
|
if err := team.LoadRepositories(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := e.Delete(&organization.TeamUser{
|
||||||
|
UID: user.ID,
|
||||||
|
OrgID: team.OrgID,
|
||||||
|
TeamID: team.ID,
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
} else if _, err = e.
|
||||||
|
ID(team.ID).
|
||||||
|
Cols("num_members").
|
||||||
|
Update(team); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete access to team repositories.
|
||||||
|
for _, repo := range team.Repos {
|
||||||
|
if err := access_model.RecalculateUserAccess(ctx, repo, user.ID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove watches from now unaccessible
|
||||||
|
if err := models.ReconsiderWatches(ctx, repo, user); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove issue assignments from now unaccessible
|
||||||
|
if err := models.ReconsiderRepoIssuesAssignee(ctx, repo, user); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return removeInvalidOrgUser(ctx, team.OrgID, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeInvalidOrgUser(ctx context.Context, orgID int64, user *user_model.User) error {
|
||||||
|
// Check if the user is a member of any team in the organization.
|
||||||
|
if count, err := db.GetEngine(ctx).Count(&organization.TeamUser{
|
||||||
|
UID: user.ID,
|
||||||
|
OrgID: orgID,
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
} else if count == 0 {
|
||||||
|
org, err := organization.GetOrgByID(ctx, orgID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return RemoveOrgUser(ctx, org, user)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -1,9 +1,10 @@
|
||||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
package models
|
package org
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -14,6 +15,8 @@ import (
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/structs"
|
||||||
|
repo_service "code.gitea.io/gitea/services/repository"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
@ -60,11 +63,6 @@ func TestTeam_RemoveMember(t *testing.T) {
|
||||||
assert.True(t, organization.IsErrLastOrgOwner(err))
|
assert.True(t, organization.IsErrLastOrgOwner(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIsUsableTeamName(t *testing.T) {
|
|
||||||
assert.NoError(t, organization.IsUsableTeamName("usable"))
|
|
||||||
assert.True(t, db.IsErrNameReserved(organization.IsUsableTeamName("new")))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewTeam(t *testing.T) {
|
func TestNewTeam(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
@ -185,3 +183,132 @@ func TestRepository_RecalculateAccesses3(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.True(t, has)
|
assert.True(t, has)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIncludesAllRepositoriesTeams(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
testTeamRepositories := func(teamID int64, repoIDs []int64) {
|
||||||
|
team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID})
|
||||||
|
assert.NoError(t, team.LoadRepositories(db.DefaultContext), "%s: GetRepositories", team.Name)
|
||||||
|
assert.Len(t, team.Repos, team.NumRepos, "%s: len repo", team.Name)
|
||||||
|
assert.Len(t, team.Repos, len(repoIDs), "%s: repo count", team.Name)
|
||||||
|
for i, rid := range repoIDs {
|
||||||
|
if rid > 0 {
|
||||||
|
assert.True(t, repo_service.HasRepository(db.DefaultContext, team, rid), "%s: HasRepository(%d) %d", rid, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get an admin user.
|
||||||
|
user, err := user_model.GetUserByID(db.DefaultContext, 1)
|
||||||
|
assert.NoError(t, err, "GetUserByID")
|
||||||
|
|
||||||
|
// Create org.
|
||||||
|
org := &organization.Organization{
|
||||||
|
Name: "All_repo",
|
||||||
|
IsActive: true,
|
||||||
|
Type: user_model.UserTypeOrganization,
|
||||||
|
Visibility: structs.VisibleTypePublic,
|
||||||
|
}
|
||||||
|
assert.NoError(t, organization.CreateOrganization(db.DefaultContext, org, user), "CreateOrganization")
|
||||||
|
|
||||||
|
// Check Owner team.
|
||||||
|
ownerTeam, err := org.GetOwnerTeam(db.DefaultContext)
|
||||||
|
assert.NoError(t, err, "GetOwnerTeam")
|
||||||
|
assert.True(t, ownerTeam.IncludesAllRepositories, "Owner team includes all repositories")
|
||||||
|
|
||||||
|
// Create repos.
|
||||||
|
repoIDs := make([]int64, 0)
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
r, err := repo_service.CreateRepositoryDirectly(db.DefaultContext, user, org.AsUser(), repo_service.CreateRepoOptions{Name: fmt.Sprintf("repo-%d", i)})
|
||||||
|
assert.NoError(t, err, "CreateRepository %d", i)
|
||||||
|
if r != nil {
|
||||||
|
repoIDs = append(repoIDs, r.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Get fresh copy of Owner team after creating repos.
|
||||||
|
ownerTeam, err = org.GetOwnerTeam(db.DefaultContext)
|
||||||
|
assert.NoError(t, err, "GetOwnerTeam")
|
||||||
|
|
||||||
|
// Create teams and check repositories.
|
||||||
|
teams := []*organization.Team{
|
||||||
|
ownerTeam,
|
||||||
|
{
|
||||||
|
OrgID: org.ID,
|
||||||
|
Name: "team one",
|
||||||
|
AccessMode: perm.AccessModeRead,
|
||||||
|
IncludesAllRepositories: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
OrgID: org.ID,
|
||||||
|
Name: "team 2",
|
||||||
|
AccessMode: perm.AccessModeRead,
|
||||||
|
IncludesAllRepositories: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
OrgID: org.ID,
|
||||||
|
Name: "team three",
|
||||||
|
AccessMode: perm.AccessModeWrite,
|
||||||
|
IncludesAllRepositories: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
OrgID: org.ID,
|
||||||
|
Name: "team 4",
|
||||||
|
AccessMode: perm.AccessModeWrite,
|
||||||
|
IncludesAllRepositories: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
teamRepos := [][]int64{
|
||||||
|
repoIDs,
|
||||||
|
repoIDs,
|
||||||
|
{},
|
||||||
|
repoIDs,
|
||||||
|
{},
|
||||||
|
}
|
||||||
|
for i, team := range teams {
|
||||||
|
if i > 0 { // first team is Owner.
|
||||||
|
assert.NoError(t, NewTeam(db.DefaultContext, team), "%s: NewTeam", team.Name)
|
||||||
|
}
|
||||||
|
testTeamRepositories(team.ID, teamRepos[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update teams and check repositories.
|
||||||
|
teams[3].IncludesAllRepositories = false
|
||||||
|
teams[4].IncludesAllRepositories = true
|
||||||
|
teamRepos[4] = repoIDs
|
||||||
|
for i, team := range teams {
|
||||||
|
assert.NoError(t, UpdateTeam(db.DefaultContext, team, false, true), "%s: UpdateTeam", team.Name)
|
||||||
|
testTeamRepositories(team.ID, teamRepos[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create repo and check teams repositories.
|
||||||
|
r, err := repo_service.CreateRepositoryDirectly(db.DefaultContext, user, org.AsUser(), repo_service.CreateRepoOptions{Name: "repo-last"})
|
||||||
|
assert.NoError(t, err, "CreateRepository last")
|
||||||
|
if r != nil {
|
||||||
|
repoIDs = append(repoIDs, r.ID)
|
||||||
|
}
|
||||||
|
teamRepos[0] = repoIDs
|
||||||
|
teamRepos[1] = repoIDs
|
||||||
|
teamRepos[4] = repoIDs
|
||||||
|
for i, team := range teams {
|
||||||
|
testTeamRepositories(team.ID, teamRepos[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove repo and check teams repositories.
|
||||||
|
assert.NoError(t, repo_service.DeleteRepositoryDirectly(db.DefaultContext, user, repoIDs[0]), "DeleteRepository")
|
||||||
|
teamRepos[0] = repoIDs[1:]
|
||||||
|
teamRepos[1] = repoIDs[1:]
|
||||||
|
teamRepos[3] = repoIDs[1:3]
|
||||||
|
teamRepos[4] = repoIDs[1:]
|
||||||
|
for i, team := range teams {
|
||||||
|
testTeamRepositories(team.ID, teamRepos[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wipe created items.
|
||||||
|
for i, rid := range repoIDs {
|
||||||
|
if i > 0 { // first repo already deleted.
|
||||||
|
assert.NoError(t, repo_service.DeleteRepositoryDirectly(db.DefaultContext, user, rid), "DeleteRepository %d", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.NoError(t, organization.DeleteOrganization(db.DefaultContext, org), "DeleteOrganization")
|
||||||
|
}
|
|
@ -2,7 +2,7 @@
|
||||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
package models
|
package org
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
package models
|
package org
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
|
@ -12,7 +12,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models"
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/models/organization"
|
"code.gitea.io/gitea/models/organization"
|
||||||
"code.gitea.io/gitea/models/perm"
|
"code.gitea.io/gitea/models/perm"
|
||||||
|
@ -448,7 +447,7 @@ func CreateRepositoryByExample(ctx context.Context, doer, u *user_model.User, re
|
||||||
}
|
}
|
||||||
for _, t := range teams {
|
for _, t := range teams {
|
||||||
if t.IncludesAllRepositories {
|
if t.IncludesAllRepositories {
|
||||||
if err := models.AddRepository(ctx, t, repo); err != nil {
|
if err := addRepositoryToTeam(ctx, t, repo); err != nil {
|
||||||
return fmt.Errorf("AddRepository: %w", err)
|
return fmt.Errorf("AddRepository: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,148 +0,0 @@
|
||||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package repository
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/models"
|
|
||||||
"code.gitea.io/gitea/models/db"
|
|
||||||
"code.gitea.io/gitea/models/organization"
|
|
||||||
"code.gitea.io/gitea/models/perm"
|
|
||||||
"code.gitea.io/gitea/models/unittest"
|
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
|
||||||
"code.gitea.io/gitea/modules/structs"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestIncludesAllRepositoriesTeams(t *testing.T) {
|
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
|
||||||
|
|
||||||
testTeamRepositories := func(teamID int64, repoIDs []int64) {
|
|
||||||
team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID})
|
|
||||||
assert.NoError(t, team.LoadRepositories(db.DefaultContext), "%s: GetRepositories", team.Name)
|
|
||||||
assert.Len(t, team.Repos, team.NumRepos, "%s: len repo", team.Name)
|
|
||||||
assert.Len(t, team.Repos, len(repoIDs), "%s: repo count", team.Name)
|
|
||||||
for i, rid := range repoIDs {
|
|
||||||
if rid > 0 {
|
|
||||||
assert.True(t, HasRepository(db.DefaultContext, team, rid), "%s: HasRepository(%d) %d", rid, i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get an admin user.
|
|
||||||
user, err := user_model.GetUserByID(db.DefaultContext, 1)
|
|
||||||
assert.NoError(t, err, "GetUserByID")
|
|
||||||
|
|
||||||
// Create org.
|
|
||||||
org := &organization.Organization{
|
|
||||||
Name: "All_repo",
|
|
||||||
IsActive: true,
|
|
||||||
Type: user_model.UserTypeOrganization,
|
|
||||||
Visibility: structs.VisibleTypePublic,
|
|
||||||
}
|
|
||||||
assert.NoError(t, organization.CreateOrganization(db.DefaultContext, org, user), "CreateOrganization")
|
|
||||||
|
|
||||||
// Check Owner team.
|
|
||||||
ownerTeam, err := org.GetOwnerTeam(db.DefaultContext)
|
|
||||||
assert.NoError(t, err, "GetOwnerTeam")
|
|
||||||
assert.True(t, ownerTeam.IncludesAllRepositories, "Owner team includes all repositories")
|
|
||||||
|
|
||||||
// Create repos.
|
|
||||||
repoIDs := make([]int64, 0)
|
|
||||||
for i := 0; i < 3; i++ {
|
|
||||||
r, err := CreateRepositoryDirectly(db.DefaultContext, user, org.AsUser(), CreateRepoOptions{Name: fmt.Sprintf("repo-%d", i)})
|
|
||||||
assert.NoError(t, err, "CreateRepository %d", i)
|
|
||||||
if r != nil {
|
|
||||||
repoIDs = append(repoIDs, r.ID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Get fresh copy of Owner team after creating repos.
|
|
||||||
ownerTeam, err = org.GetOwnerTeam(db.DefaultContext)
|
|
||||||
assert.NoError(t, err, "GetOwnerTeam")
|
|
||||||
|
|
||||||
// Create teams and check repositories.
|
|
||||||
teams := []*organization.Team{
|
|
||||||
ownerTeam,
|
|
||||||
{
|
|
||||||
OrgID: org.ID,
|
|
||||||
Name: "team one",
|
|
||||||
AccessMode: perm.AccessModeRead,
|
|
||||||
IncludesAllRepositories: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
OrgID: org.ID,
|
|
||||||
Name: "team 2",
|
|
||||||
AccessMode: perm.AccessModeRead,
|
|
||||||
IncludesAllRepositories: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
OrgID: org.ID,
|
|
||||||
Name: "team three",
|
|
||||||
AccessMode: perm.AccessModeWrite,
|
|
||||||
IncludesAllRepositories: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
OrgID: org.ID,
|
|
||||||
Name: "team 4",
|
|
||||||
AccessMode: perm.AccessModeWrite,
|
|
||||||
IncludesAllRepositories: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
teamRepos := [][]int64{
|
|
||||||
repoIDs,
|
|
||||||
repoIDs,
|
|
||||||
{},
|
|
||||||
repoIDs,
|
|
||||||
{},
|
|
||||||
}
|
|
||||||
for i, team := range teams {
|
|
||||||
if i > 0 { // first team is Owner.
|
|
||||||
assert.NoError(t, models.NewTeam(db.DefaultContext, team), "%s: NewTeam", team.Name)
|
|
||||||
}
|
|
||||||
testTeamRepositories(team.ID, teamRepos[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update teams and check repositories.
|
|
||||||
teams[3].IncludesAllRepositories = false
|
|
||||||
teams[4].IncludesAllRepositories = true
|
|
||||||
teamRepos[4] = repoIDs
|
|
||||||
for i, team := range teams {
|
|
||||||
assert.NoError(t, models.UpdateTeam(db.DefaultContext, team, false, true), "%s: UpdateTeam", team.Name)
|
|
||||||
testTeamRepositories(team.ID, teamRepos[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create repo and check teams repositories.
|
|
||||||
r, err := CreateRepositoryDirectly(db.DefaultContext, user, org.AsUser(), CreateRepoOptions{Name: "repo-last"})
|
|
||||||
assert.NoError(t, err, "CreateRepository last")
|
|
||||||
if r != nil {
|
|
||||||
repoIDs = append(repoIDs, r.ID)
|
|
||||||
}
|
|
||||||
teamRepos[0] = repoIDs
|
|
||||||
teamRepos[1] = repoIDs
|
|
||||||
teamRepos[4] = repoIDs
|
|
||||||
for i, team := range teams {
|
|
||||||
testTeamRepositories(team.ID, teamRepos[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove repo and check teams repositories.
|
|
||||||
assert.NoError(t, DeleteRepositoryDirectly(db.DefaultContext, user, repoIDs[0]), "DeleteRepository")
|
|
||||||
teamRepos[0] = repoIDs[1:]
|
|
||||||
teamRepos[1] = repoIDs[1:]
|
|
||||||
teamRepos[3] = repoIDs[1:3]
|
|
||||||
teamRepos[4] = repoIDs[1:]
|
|
||||||
for i, team := range teams {
|
|
||||||
testTeamRepositories(team.ID, teamRepos[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wipe created items.
|
|
||||||
for i, rid := range repoIDs {
|
|
||||||
if i > 0 { // first repo already deleted.
|
|
||||||
assert.NoError(t, DeleteRepositoryDirectly(db.DefaultContext, user, rid), "DeleteRepository %d", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert.NoError(t, organization.DeleteOrganization(db.DefaultContext, org), "DeleteOrganization")
|
|
||||||
}
|
|
|
@ -348,87 +348,6 @@ func DeleteRepositoryDirectly(ctx context.Context, doer *user_model.User, repoID
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// removeRepositoryFromTeam removes a repository from a team and recalculates access
|
|
||||||
// Note: Repository shall not be removed from team if it includes all repositories (unless the repository is deleted)
|
|
||||||
func removeRepositoryFromTeam(ctx context.Context, t *organization.Team, repo *repo_model.Repository, recalculate bool) (err error) {
|
|
||||||
e := db.GetEngine(ctx)
|
|
||||||
if err = organization.RemoveTeamRepo(ctx, t.ID, repo.ID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
t.NumRepos--
|
|
||||||
if _, err = e.ID(t.ID).Cols("num_repos").Update(t); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't need to recalculate when delete a repository from organization.
|
|
||||||
if recalculate {
|
|
||||||
if err = access_model.RecalculateTeamAccesses(ctx, repo, t.ID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
teamMembers, err := organization.GetTeamMembers(ctx, &organization.SearchMembersOptions{
|
|
||||||
TeamID: t.ID,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("GetTeamMembers: %w", err)
|
|
||||||
}
|
|
||||||
for _, member := range teamMembers {
|
|
||||||
has, err := access_model.HasAnyUnitAccess(ctx, member.ID, repo)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
} else if has {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = repo_model.WatchRepo(ctx, member, repo, false); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove all IssueWatches a user has subscribed to in the repositories
|
|
||||||
if err := issues_model.RemoveIssueWatchersByRepoID(ctx, member.ID, repo.ID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// HasRepository returns true if given repository belong to team.
|
|
||||||
func HasRepository(ctx context.Context, t *organization.Team, repoID int64) bool {
|
|
||||||
return organization.HasTeamRepo(ctx, t.OrgID, t.ID, repoID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveRepositoryFromTeam removes repository from team of organization.
|
|
||||||
// If the team shall include all repositories the request is ignored.
|
|
||||||
func RemoveRepositoryFromTeam(ctx context.Context, t *organization.Team, repoID int64) error {
|
|
||||||
if !HasRepository(ctx, t, repoID) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if t.IncludesAllRepositories {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
repo, err := repo_model.GetRepositoryByID(ctx, repoID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, committer, err := db.TxContext(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer committer.Close()
|
|
||||||
|
|
||||||
if err = removeRepositoryFromTeam(ctx, t, repo, true); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return committer.Commit()
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteOwnerRepositoriesDirectly calls DeleteRepositoryDirectly for all repos of the given owner
|
// DeleteOwnerRepositoriesDirectly calls DeleteRepositoryDirectly for all repos of the given owner
|
||||||
func DeleteOwnerRepositoriesDirectly(ctx context.Context, owner *user_model.User) error {
|
func DeleteOwnerRepositoriesDirectly(ctx context.Context, owner *user_model.User) error {
|
||||||
for {
|
for {
|
||||||
|
|
226
services/repository/repo_team.go
Normal file
226
services/repository/repo_team.go
Normal file
|
@ -0,0 +1,226 @@
|
||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
|
"code.gitea.io/gitea/models/organization"
|
||||||
|
access_model "code.gitea.io/gitea/models/perm/access"
|
||||||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TeamAddRepository adds new repository to team of organization.
|
||||||
|
func TeamAddRepository(ctx context.Context, t *organization.Team, repo *repo_model.Repository) (err error) {
|
||||||
|
if repo.OwnerID != t.OrgID {
|
||||||
|
return errors.New("repository does not belong to organization")
|
||||||
|
} else if organization.HasTeamRepo(ctx, t.OrgID, t.ID, repo.ID) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||||
|
return addRepositoryToTeam(ctx, t, repo)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func addRepositoryToTeam(ctx context.Context, t *organization.Team, repo *repo_model.Repository) (err error) {
|
||||||
|
if err = organization.AddTeamRepo(ctx, t.OrgID, t.ID, repo.ID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = organization.IncrTeamRepoNum(ctx, t.ID); err != nil {
|
||||||
|
return fmt.Errorf("update team: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.NumRepos++
|
||||||
|
|
||||||
|
if err = access_model.RecalculateTeamAccesses(ctx, repo, 0); err != nil {
|
||||||
|
return fmt.Errorf("recalculateAccesses: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make all team members watch this repo if enabled in global settings
|
||||||
|
if setting.Service.AutoWatchNewRepos {
|
||||||
|
if err = t.LoadMembers(ctx); err != nil {
|
||||||
|
return fmt.Errorf("getMembers: %w", err)
|
||||||
|
}
|
||||||
|
for _, u := range t.Members {
|
||||||
|
if err = repo_model.WatchRepo(ctx, u, repo, true); err != nil {
|
||||||
|
return fmt.Errorf("watchRepo: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddAllRepositoriesToTeam adds all repositories to the team.
|
||||||
|
// If the team already has some repositories they will be left unchanged.
|
||||||
|
func AddAllRepositoriesToTeam(ctx context.Context, t *organization.Team) error {
|
||||||
|
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||||
|
orgRepos, err := organization.GetOrgRepositories(ctx, t.OrgID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("get org repos: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, repo := range orgRepos {
|
||||||
|
if !organization.HasTeamRepo(ctx, t.OrgID, t.ID, repo.ID) {
|
||||||
|
if err := addRepositoryToTeam(ctx, t, repo); err != nil {
|
||||||
|
return fmt.Errorf("AddRepository: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveAllRepositoriesFromTeam removes all repositories from team and recalculates access
|
||||||
|
func RemoveAllRepositoriesFromTeam(ctx context.Context, t *organization.Team) (err error) {
|
||||||
|
if t.IncludesAllRepositories {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, committer, err := db.TxContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer committer.Close()
|
||||||
|
|
||||||
|
if err = removeAllRepositoriesFromTeam(ctx, t); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return committer.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeAllRepositoriesFromTeam removes all repositories from team and recalculates access
|
||||||
|
// Note: Shall not be called if team includes all repositories
|
||||||
|
func removeAllRepositoriesFromTeam(ctx context.Context, t *organization.Team) (err error) {
|
||||||
|
e := db.GetEngine(ctx)
|
||||||
|
// Delete all accesses.
|
||||||
|
for _, repo := range t.Repos {
|
||||||
|
if err := access_model.RecalculateTeamAccesses(ctx, repo, t.ID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove watches from all users and now unaccessible repos
|
||||||
|
for _, user := range t.Members {
|
||||||
|
has, err := access_model.HasAnyUnitAccess(ctx, user.ID, repo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if has {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = repo_model.WatchRepo(ctx, user, repo, false); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove all IssueWatches a user has subscribed to in the repositories
|
||||||
|
if err = issues_model.RemoveIssueWatchersByRepoID(ctx, user.ID, repo.ID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete team-repo
|
||||||
|
if _, err := e.
|
||||||
|
Where("team_id=?", t.ID).
|
||||||
|
Delete(new(organization.TeamRepo)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
t.NumRepos = 0
|
||||||
|
if _, err = e.ID(t.ID).Cols("num_repos").Update(t); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveRepositoryFromTeam removes repository from team of organization.
|
||||||
|
// If the team shall include all repositories the request is ignored.
|
||||||
|
func RemoveRepositoryFromTeam(ctx context.Context, t *organization.Team, repoID int64) error {
|
||||||
|
if !HasRepository(ctx, t, repoID) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.IncludesAllRepositories {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
repo, err := repo_model.GetRepositoryByID(ctx, repoID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, committer, err := db.TxContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer committer.Close()
|
||||||
|
|
||||||
|
if err = removeRepositoryFromTeam(ctx, t, repo, true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return committer.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeRepositoryFromTeam removes a repository from a team and recalculates access
|
||||||
|
// Note: Repository shall not be removed from team if it includes all repositories (unless the repository is deleted)
|
||||||
|
func removeRepositoryFromTeam(ctx context.Context, t *organization.Team, repo *repo_model.Repository, recalculate bool) (err error) {
|
||||||
|
e := db.GetEngine(ctx)
|
||||||
|
if err = organization.RemoveTeamRepo(ctx, t.ID, repo.ID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
t.NumRepos--
|
||||||
|
if _, err = e.ID(t.ID).Cols("num_repos").Update(t); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't need to recalculate when delete a repository from organization.
|
||||||
|
if recalculate {
|
||||||
|
if err = access_model.RecalculateTeamAccesses(ctx, repo, t.ID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
teamMembers, err := organization.GetTeamMembers(ctx, &organization.SearchMembersOptions{
|
||||||
|
TeamID: t.ID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("GetTeamMembers: %w", err)
|
||||||
|
}
|
||||||
|
for _, member := range teamMembers {
|
||||||
|
has, err := access_model.HasAnyUnitAccess(ctx, member.ID, repo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if has {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = repo_model.WatchRepo(ctx, member, repo, false); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove all IssueWatches a user has subscribed to in the repositories
|
||||||
|
if err := issues_model.RemoveIssueWatchersByRepoID(ctx, member.ID, repo.ID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasRepository returns true if given repository belong to team.
|
||||||
|
func HasRepository(ctx context.Context, t *organization.Team, repoID int64) bool {
|
||||||
|
return organization.HasTeamRepo(ctx, t.OrgID, t.ID, repoID)
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
package org
|
package repository
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
|
@ -59,7 +59,7 @@ func TransferOwnership(ctx context.Context, doer, newOwner *user_model.User, rep
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, team := range teams {
|
for _, team := range teams {
|
||||||
if err := models.AddRepository(ctx, team, newRepo); err != nil {
|
if err := addRepositoryToTeam(ctx, team, newRepo); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -205,7 +205,7 @@ func transferOwnership(ctx context.Context, doer *user_model.User, newOwnerName
|
||||||
}
|
}
|
||||||
for _, t := range teams {
|
for _, t := range teams {
|
||||||
if t.IncludesAllRepositories {
|
if t.IncludesAllRepositories {
|
||||||
if err := models.AddRepository(ctx, t, repo); err != nil {
|
if err := addRepositoryToTeam(ctx, t, repo); err != nil {
|
||||||
return fmt.Errorf("AddRepository: %w", err)
|
return fmt.Errorf("AddRepository: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -188,7 +188,7 @@ func DeleteUser(ctx context.Context, u *user_model.User, purge bool) error {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
for _, org := range orgs {
|
for _, org := range orgs {
|
||||||
if err := models.RemoveOrgUser(ctx, org, u); err != nil {
|
if err := org_service.RemoveOrgUser(ctx, org, u); err != nil {
|
||||||
if organization.IsErrLastOrgOwner(err) {
|
if organization.IsErrLastOrgOwner(err) {
|
||||||
err = org_service.DeleteOrganization(ctx, org, true)
|
err = org_service.DeleteOrganization(ctx, org, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
org_service "code.gitea.io/gitea/services/org"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
@ -44,7 +45,7 @@ func TestDeleteUser(t *testing.T) {
|
||||||
assert.NoError(t, db.GetEngine(db.DefaultContext).Find(&orgUsers, &organization.OrgUser{UID: userID}))
|
assert.NoError(t, db.GetEngine(db.DefaultContext).Find(&orgUsers, &organization.OrgUser{UID: userID}))
|
||||||
for _, orgUser := range orgUsers {
|
for _, orgUser := range orgUsers {
|
||||||
org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: orgUser.OrgID})
|
org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: orgUser.OrgID})
|
||||||
if err := models.RemoveOrgUser(db.DefaultContext, org, user); err != nil {
|
if err := org_service.RemoveOrgUser(db.DefaultContext, org, user); err != nil {
|
||||||
assert.True(t, organization.IsErrLastOrgOwner(err))
|
assert.True(t, organization.IsErrLastOrgOwner(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models"
|
|
||||||
auth_model "code.gitea.io/gitea/models/auth"
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/models/organization"
|
"code.gitea.io/gitea/models/organization"
|
||||||
|
@ -19,6 +18,7 @@ import (
|
||||||
"code.gitea.io/gitea/modules/translation"
|
"code.gitea.io/gitea/modules/translation"
|
||||||
"code.gitea.io/gitea/services/auth"
|
"code.gitea.io/gitea/services/auth"
|
||||||
"code.gitea.io/gitea/services/auth/source/ldap"
|
"code.gitea.io/gitea/services/auth/source/ldap"
|
||||||
|
org_service "code.gitea.io/gitea/services/org"
|
||||||
"code.gitea.io/gitea/tests"
|
"code.gitea.io/gitea/tests"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
@ -429,9 +429,9 @@ func TestLDAPGroupTeamSyncAddMember(t *testing.T) {
|
||||||
isMember, err := organization.IsTeamMember(db.DefaultContext, usersOrgs[0].ID, team.ID, user.ID)
|
isMember, err := organization.IsTeamMember(db.DefaultContext, usersOrgs[0].ID, team.ID, user.ID)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.True(t, isMember, "Membership should be added to the right team")
|
assert.True(t, isMember, "Membership should be added to the right team")
|
||||||
err = models.RemoveTeamMember(db.DefaultContext, team, user)
|
err = org_service.RemoveTeamMember(db.DefaultContext, team, user)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
err = models.RemoveOrgUser(db.DefaultContext, usersOrgs[0], user)
|
err = org_service.RemoveOrgUser(db.DefaultContext, usersOrgs[0], user)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
} else {
|
} else {
|
||||||
// assert members of LDAP group "cn=admin_staff" keep initial team membership since mapped team does not exist
|
// assert members of LDAP group "cn=admin_staff" keep initial team membership since mapped team does not exist
|
||||||
|
@ -461,7 +461,7 @@ func TestLDAPGroupTeamSyncRemoveMember(t *testing.T) {
|
||||||
})
|
})
|
||||||
err = organization.AddOrgUser(db.DefaultContext, org.ID, user.ID)
|
err = organization.AddOrgUser(db.DefaultContext, org.ID, user.ID)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
err = models.AddTeamMember(db.DefaultContext, team, user)
|
err = org_service.AddTeamMember(db.DefaultContext, team, user)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
isMember, err := organization.IsOrganizationMember(db.DefaultContext, org.ID, user.ID)
|
isMember, err := organization.IsOrganizationMember(db.DefaultContext, org.ID, user.ID)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
Loading…
Reference in a new issue