diff --git a/models/organization/team.go b/models/organization/team.go index fb7f0c04939e6..9758d88775135 100644 --- a/models/organization/team.go +++ b/models/organization/team.go @@ -272,3 +272,8 @@ func IncrTeamRepoNum(ctx context.Context, teamID int64) error { _, err := db.GetEngine(ctx).Incr("num_repos").ID(teamID).Update(new(Team)) return err } + +func UpdateTeam(ctx context.Context, t *Team, cols ...string) error { + _, err := db.GetEngine(ctx).ID(t.ID).Cols(cols...).Update(t) + return err +} diff --git a/models/organization/team_fix.go b/models/organization/team_fix.go new file mode 100644 index 0000000000000..42f852167d37a --- /dev/null +++ b/models/organization/team_fix.go @@ -0,0 +1,72 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package organization + +import ( + "context" + "strings" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/perm" + "code.gitea.io/gitea/models/unit" + + "xorm.io/builder" +) + +// CountInconsistentOwnerTeams returns the amount of owner teams that any of +// their access modes not set to the right value. For external trackers and external wikis +// it should be Read and otherwise it should be Owner. +func CountInconsistentOwnerTeams(ctx context.Context) (int64, error) { + cnt1, err := db.GetEngine(ctx).Table("team"). + Join("INNER", "team_unit", "`team`.id = `team_unit`.team_id"). + Where("`team`.lower_name = ?", strings.ToLower(OwnerTeamName)). + And(builder.Or(builder.Eq{"`team_unit`.`type`": unit.TypeExternalTracker}, builder.Eq{"`team_unit`.`type`": unit.TypeExternalWiki})). + And("`team_unit`.`access_mode` <> ?", perm.AccessModeRead). + GroupBy("`team`.`id`"). + Count() + if err != nil { + return 0, err + } + + cnt2, err := db.GetEngine(ctx).Table("team"). + Join("INNER", "team_unit", "`team`.id = `team_unit`.team_id"). + Where("`team`.lower_name = ?", strings.ToLower(OwnerTeamName)). + And(builder.And(builder.Neq{"`team_unit`.`type`": unit.TypeExternalTracker}, builder.Neq{"`team_unit`.`type`": unit.TypeExternalWiki})). + And("`team_unit`.`access_mode` <> ?", perm.AccessModeOwner). + GroupBy("`team_unit`.team_id"). + Count() + if err != nil { + return 0, err + } + return cnt1 + cnt2, nil +} + +// FixInconsistentOwnerTeams fixes inconsistent owner teams that of +// their access modes not set to the right value. For external trackers and external wikis +// it should be Read and otherwise it should be Owner. +func FixInconsistentOwnerTeams(ctx context.Context) (int64, error) { + subQuery := builder.Select("id").From("team").Where(builder.Eq{"lower_name": strings.ToLower(OwnerTeamName)}) + updated1, err := db.GetEngine(ctx).Table("team_unit"). + Where(builder.Or(builder.Eq{"`team_unit`.`type`": unit.TypeExternalTracker}, builder.Eq{"`team_unit`.`type`": unit.TypeExternalWiki})). + In("team_id", subQuery). + Cols("access_mode"). + Update(&TeamUnit{ + AccessMode: perm.AccessModeRead, + }) + if err != nil { + return 0, err + } + + updated2, err := db.GetEngine(ctx).Table("team_unit"). + Where(builder.And(builder.Neq{"`team_unit`.`type`": unit.TypeExternalTracker}, builder.Neq{"`team_unit`.`type`": unit.TypeExternalWiki})). + In("team_id", subQuery). + Cols("access_mode"). + Update(&TeamUnit{ + AccessMode: perm.AccessModeOwner, + }) + if err != nil { + return 0, err + } + return updated1 + updated2, nil +} diff --git a/routers/api/v1/org/team.go b/routers/api/v1/org/team.go index bc50960b61b11..fc02288cb2a5d 100644 --- a/routers/api/v1/org/team.go +++ b/routers/api/v1/org/team.go @@ -293,45 +293,46 @@ func EditTeam(ctx *context.APIContext) { team.CanCreateOrgRepo = team.IsOwnerTeam() || *form.CanCreateOrgRepo } + teamName := team.Name if len(form.Name) > 0 { - team.Name = form.Name + teamName = form.Name } + description := team.Description if form.Description != nil { - team.Description = *form.Description + description = *form.Description } - isAuthChanged := false - isIncludeAllChanged := false - if !team.IsOwnerTeam() && len(form.Permission) != 0 { - // Validate permission level. - p := perm.ParseAccessMode(form.Permission) - if p < perm.AccessModeAdmin && len(form.UnitsMap) > 0 { - p = unit_model.MinUnitAccessMode(convertUnitsMap(form.UnitsMap)) - } - - if team.AccessMode != p { - isAuthChanged = true - team.AccessMode = p - } + includeAllRepos := team.IncludesAllRepositories + if form.IncludesAllRepositories != nil { + includeAllRepos = *form.IncludesAllRepositories + } - if form.IncludesAllRepositories != nil { - isIncludeAllChanged = true - team.IncludesAllRepositories = *form.IncludesAllRepositories - } + canCreateOrgRepo := team.CanCreateOrgRepo + if form.CanCreateOrgRepo != nil { + canCreateOrgRepo = *form.CanCreateOrgRepo } + unitPerms := make(map[unit_model.Type]perm.AccessMode) if team.AccessMode < perm.AccessModeAdmin { if len(form.UnitsMap) > 0 { - attachTeamUnitsMap(team, form.UnitsMap) - } else if len(form.Units) > 0 { - attachTeamUnits(team, form.Units) + unitPerms = convertUnitsMap(form.UnitsMap) } - } else { - attachAdminTeamUnits(team) } - if err := org_service.UpdateTeam(ctx, team, isAuthChanged, isIncludeAllChanged); err != nil { + isAdmin := team.AccessMode == perm.AccessModeAdmin + if form.Permission != "" && form.Permission == "admin" { + isAdmin = true + } + + if err := org_service.UpdateTeam(ctx, team, org_service.UpdateTeamOptions{ + TeamName: teamName, + Description: description, + IsAdmin: isAdmin, + IncludesAllRepositories: includeAllRepos, + CanCreateOrgRepo: canCreateOrgRepo, + UnitPerms: unitPerms, + }); err != nil { ctx.Error(http.StatusInternalServerError, "EditTeam", err) return } diff --git a/routers/web/org/teams.go b/routers/web/org/teams.go index bd78832103251..36f6786f534c5 100644 --- a/routers/web/org/teams.go +++ b/routers/web/org/teams.go @@ -481,61 +481,26 @@ func EditTeam(ctx *context.Context) { func EditTeamPost(ctx *context.Context) { form := web.GetForm(ctx).(*forms.CreateTeamForm) t := ctx.Org.Team - newAccessMode := perm.ParseAccessMode(form.Permission) - unitPerms := getUnitPerms(ctx.Req.Form, newAccessMode) - if newAccessMode < perm.AccessModeAdmin { - // if newAccessMode is less than admin accessmode, then it should be general accessmode, - // so we should calculate the minial accessmode from units accessmodes. - newAccessMode = unit_model.MinUnitAccessMode(unitPerms) - } - isAuthChanged := false - isIncludeAllChanged := false - includesAllRepositories := form.RepoAccess == "all" + unitPerms := getUnitPerms(ctx.Req.Form, perm.ParseAccessMode(form.Permission)) ctx.Data["Title"] = ctx.Org.Organization.FullName ctx.Data["PageIsOrgTeams"] = true ctx.Data["Team"] = t ctx.Data["Units"] = unit_model.Units - if !t.IsOwnerTeam() { - t.Name = form.TeamName - if t.AccessMode != newAccessMode { - isAuthChanged = true - t.AccessMode = newAccessMode - } - - if t.IncludesAllRepositories != includesAllRepositories { - isIncludeAllChanged = true - t.IncludesAllRepositories = includesAllRepositories - } - t.CanCreateOrgRepo = form.CanCreateOrgRepo - } else { - t.CanCreateOrgRepo = true - } - - t.Description = form.Description - units := make([]*org_model.TeamUnit, 0, len(unitPerms)) - for tp, perm := range unitPerms { - units = append(units, &org_model.TeamUnit{ - OrgID: t.OrgID, - TeamID: t.ID, - Type: tp, - AccessMode: perm, - }) - } - t.Units = units - - if ctx.HasError() { - ctx.HTML(http.StatusOK, tplTeamNew) - return - } - if t.AccessMode < perm.AccessModeAdmin && len(unitPerms) == 0 { ctx.RenderWithErr(ctx.Tr("form.team_no_units_error"), tplTeamNew, &form) return } - if err := org_service.UpdateTeam(ctx, t, isAuthChanged, isIncludeAllChanged); err != nil { + if err := org_service.UpdateTeam(ctx, t, org_service.UpdateTeamOptions{ + TeamName: form.TeamName, + Description: form.Description, + IsAdmin: form.Permission == "admin", + IncludesAllRepositories: form.RepoAccess == "all", + CanCreateOrgRepo: form.CanCreateOrgRepo, + UnitPerms: unitPerms, + }); err != nil { ctx.Data["Err_TeamName"] = true switch { case org_model.IsErrTeamAlreadyExist(err): diff --git a/services/doctor/dbconsistency.go b/services/doctor/dbconsistency.go index 7cb7445148a0b..1527a46286673 100644 --- a/services/doctor/dbconsistency.go +++ b/services/doctor/dbconsistency.go @@ -11,6 +11,7 @@ import ( "code.gitea.io/gitea/models/db" issues_model "code.gitea.io/gitea/models/issues" "code.gitea.io/gitea/models/migrations" + org_model "code.gitea.io/gitea/models/organization" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -164,6 +165,12 @@ func prepareDBConsistencyChecks() []consistencyCheck { Fixer: repo_model.DeleteOrphanedTopics, FixedMessage: "Removed", }, + { + Name: "Owner teams with wrong admin access unit", + Counter: org_model.CountInconsistentOwnerTeams, + Fixer: org_model.FixInconsistentOwnerTeams, + FixedMessage: "Fixed", + }, } // TODO: function to recalc all counters diff --git a/services/doctor/fix8312.go b/services/doctor/fix8312.go index 3e2ca68eb42f9..8710dd31d22fb 100644 --- a/services/doctor/fix8312.go +++ b/services/doctor/fix8312.go @@ -10,7 +10,6 @@ import ( org_model "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" "code.gitea.io/gitea/modules/log" - org_service "code.gitea.io/gitea/services/org" "xorm.io/builder" ) @@ -29,7 +28,7 @@ func fixOwnerTeamCreateOrgRepo(ctx context.Context, logger log.Logger, autofix b return nil } - return org_service.UpdateTeam(ctx, team, false, false) + return org_model.UpdateTeam(ctx, team, "can_create_org_repo") }, ) if err != nil { diff --git a/services/forms/org.go b/services/forms/org.go index db182f7e96b5b..4d85897e90ccb 100644 --- a/services/forms/org.go +++ b/services/forms/org.go @@ -53,19 +53,12 @@ func (f *UpdateOrgSettingForm) Validate(req *http.Request, errs binding.Errors) return middleware.Validate(errs, ctx.Data, f, ctx.Locale) } -// ___________ -// \__ ___/___ _____ _____ -// | |_/ __ \\__ \ / \ -// | |\ ___/ / __ \| Y Y \ -// |____| \___ >____ /__|_| / -// \/ \/ \/ - // CreateTeamForm form for creating team type CreateTeamForm struct { TeamName string `binding:"Required;AlphaDashDot;MaxSize(255)"` Description string `binding:"MaxSize(255)"` - Permission string - RepoAccess string + Permission string `binding:"Required;In(,read,write,admin)"` + RepoAccess string `binding:"Required;In(specified, all)"` CanCreateOrgRepo bool } diff --git a/services/org/team.go b/services/org/team.go index 3688e684339db..2e3c1bd125f2a 100644 --- a/services/org/team.go +++ b/services/org/team.go @@ -12,8 +12,10 @@ import ( git_model "code.gitea.io/gitea/models/git" issues_model "code.gitea.io/gitea/models/issues" "code.gitea.io/gitea/models/organization" + "code.gitea.io/gitea/models/perm" access_model "code.gitea.io/gitea/models/perm/access" repo_model "code.gitea.io/gitea/models/repo" + unit_model "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" @@ -89,12 +91,88 @@ func NewTeam(ctx context.Context, t *organization.Team) (err error) { return committer.Commit() } +type UpdateTeamOptions struct { + TeamName string + Description string + IsAdmin bool + IncludesAllRepositories bool + CanCreateOrgRepo bool + UnitPerms map[unit_model.Type]perm.AccessMode +} + +func UpdateTeam(ctx context.Context, team *organization.Team, opts UpdateTeamOptions) error { + var changedCols []string + + newAccessMode := team.AccessMode + if opts.IsAdmin { + newAccessMode = perm.AccessModeAdmin + } else if len(opts.UnitPerms) > 0 { + // if newAccessMode is less than admin accessmode, then it should be general accessmode, + // so we should calculate the minial accessmode from units accessmodes. + newAccessMode = unit_model.MinUnitAccessMode(opts.UnitPerms) + } + + if !team.IsOwnerTeam() { + if team.Name != opts.TeamName { + team.Name = opts.TeamName + team.LowerName = strings.ToLower(opts.TeamName) + changedCols = append(changedCols, "name", "lower_name") + } + + if team.AccessMode != newAccessMode { + team.AccessMode = newAccessMode + changedCols = append(changedCols, "authorize") + } + + if team.IncludesAllRepositories != opts.IncludesAllRepositories { + team.IncludesAllRepositories = opts.IncludesAllRepositories + changedCols = append(changedCols, "includes_all_repositories") + } + if len(opts.UnitPerms) > 0 { + units := make([]*organization.TeamUnit, 0, len(opts.UnitPerms)) + for tp, perm := range opts.UnitPerms { + units = append(units, &organization.TeamUnit{ + OrgID: team.OrgID, + TeamID: team.ID, + Type: tp, + AccessMode: perm, + }) + } + team.Units = units + changedCols = append(changedCols, "units") + } + if team.CanCreateOrgRepo != opts.CanCreateOrgRepo { + team.CanCreateOrgRepo = opts.CanCreateOrgRepo + changedCols = append(changedCols, "can_create_org_repo") + } + } else { // make the possible legacy data correct, we force to update these fields + team.CanCreateOrgRepo = true + team.IncludesAllRepositories = true + changedCols = append(changedCols, "can_create_org_repo", "includes_all_repositories") + } + + if team.Description != opts.Description { + changedCols = append(changedCols, "description") + team.Description = opts.Description + } + + return organization.UpdateTeam(ctx, team, changedCols...) +} + // UpdateTeam updates information of team. -func UpdateTeam(ctx context.Context, t *organization.Team, authChanged, includeAllChanged bool) (err error) { +/*func UpdateTeam(ctx context.Context, t *organization.Team, updateCols ...string) (err error) { if len(t.Name) == 0 { return util.NewInvalidArgumentErrorf("empty team name") } + if slices.Contains(updateCols, "name") && !slices.Contains(updateCols, "lower_name") { + t.LowerName = strings.ToLower(t.Name) + updateCols = append(updateCols, "lower_name") + } + + authChanged := slices.Contains(updateCols, "authorize") + includeAllChanged := slices.Contains(updateCols, "includes_all_repositories") + if len(t.Description) > 255 { t.Description = t.Description[:255] } @@ -118,13 +196,12 @@ func UpdateTeam(ctx context.Context, t *organization.Team, authChanged, includeA } 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 { + if _, err = sess.ID(t.ID).Cols(updateCols...).Update(t); err != nil { return fmt.Errorf("update: %w", err) } // update units for team - if len(t.Units) > 0 { + if slices.Contains(updateCols, "units") { for _, unit := range t.Units { unit.TeamID = t.ID } @@ -161,7 +238,7 @@ func UpdateTeam(ctx context.Context, t *organization.Team, authChanged, includeA } return committer.Commit() -} +}*/ // DeleteTeam deletes given team. // It's caller's responsibility to assign organization ID. diff --git a/services/org/team_test.go b/services/org/team_test.go index 58b8e0803c4d2..2617c5a09069d 100644 --- a/services/org/team_test.go +++ b/services/org/team_test.go @@ -78,11 +78,14 @@ func TestUpdateTeam(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 2}) - team.LowerName = "newname" team.Name = "newName" team.Description = strings.Repeat("A long description!", 100) team.AccessMode = perm.AccessModeAdmin - assert.NoError(t, UpdateTeam(db.DefaultContext, team, true, false)) + assert.NoError(t, UpdateTeam(db.DefaultContext, team, UpdateTeamOptions{ + TeamName: "name", + Description: "description", + IsAdmin: true, + })) team = unittest.AssertExistsAndLoadBean(t, &organization.Team{Name: "newName"}) assert.True(t, strings.HasPrefix(team.Description, "A long description!")) @@ -101,7 +104,10 @@ func TestUpdateTeam2(t *testing.T) { team.LowerName = "owners" team.Name = "Owners" team.Description = strings.Repeat("A long description!", 100) - err := UpdateTeam(db.DefaultContext, team, true, false) + err := UpdateTeam(db.DefaultContext, team, UpdateTeamOptions{ + TeamName: "name", + Description: "description", + }) assert.True(t, organization.IsErrTeamAlreadyExist(err)) unittest.CheckConsistencyFor(t, &organization.Team{ID: team.ID}) @@ -277,7 +283,10 @@ func TestIncludesAllRepositoriesTeams(t *testing.T) { 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) + assert.NoError(t, UpdateTeam(db.DefaultContext, team, UpdateTeamOptions{ + IncludesAllRepositories: false, + CanCreateOrgRepo: true, + }), "%s: UpdateTeam", team.Name) testTeamRepositories(team.ID, teamRepos[i]) } diff --git a/tests/integration/api_team_test.go b/tests/integration/api_team_test.go index d14c66ff2ca64..28a400973a048 100644 --- a/tests/integration/api_team_test.go +++ b/tests/integration/api_team_test.go @@ -11,7 +11,7 @@ import ( auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" - "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/repo" "code.gitea.io/gitea/models/unit" @@ -27,9 +27,9 @@ import ( func TestAPITeam(t *testing.T) { defer tests.PrepareTestEnv(t)() - teamUser := unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{ID: 1}) - team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamUser.TeamID}) - org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: teamUser.OrgID}) + teamUser := unittest.AssertExistsAndLoadBean(t, &org_model.TeamUser{ID: 1}) + team := unittest.AssertExistsAndLoadBean(t, &org_model.Team{ID: teamUser.TeamID}) + org := unittest.AssertExistsAndLoadBean(t, &org_model.Organization{ID: teamUser.OrgID}) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: teamUser.UID}) session := loginUser(t, user.Name) @@ -42,10 +42,11 @@ func TestAPITeam(t *testing.T) { DecodeJSON(t, resp, &apiTeam) assert.EqualValues(t, team.ID, apiTeam.ID) assert.Equal(t, team.Name, apiTeam.Name) + assert.Equal(t, team.Description, apiTeam.Description) assert.EqualValues(t, convert.ToOrganization(db.DefaultContext, org), apiTeam.Organization) // non team member user will not access the teams details - teamUser2 := unittest.AssertExistsAndLoadBean(t, &organization.TeamUser{ID: 3}) + teamUser2 := unittest.AssertExistsAndLoadBean(t, &org_model.TeamUser{ID: 3}) user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: teamUser2.UID}) session = loginUser(t, user2.Name) @@ -59,10 +60,30 @@ func TestAPITeam(t *testing.T) { // Get an admin user able to create, update and delete teams. user = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + org = unittest.AssertExistsAndLoadBean(t, &org_model.Organization{ID: 6}) session = loginUser(t, user.Name) token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization) - org = unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 6}) + // edit owner team + editDescription := "this is owner team" + editFalse := false + teamToEdit := &api.EditTeamOption{ + Name: "not_owner_team", // should be ignored + Description: &editDescription, + Permission: "admin", // should be ignored + IncludesAllRepositories: &editFalse, // should be ignored + Units: []string{"repo.code", "repo.pulls", "repo.releases"}, // should be ignored + } + + req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d", team.ID), teamToEdit). + AddTokenAuth(token) + resp = MakeRequest(t, req, http.StatusOK) + apiTeam = api.Team{} + DecodeJSON(t, resp, &apiTeam) + checkTeamResponse(t, "Edit Owner Team", &apiTeam, org_model.OwnerTeamName, *teamToEdit.Description, true, + "owner", unit.AllUnitKeyNames(), nil) + checkTeamBean(t, apiTeam.ID, org_model.OwnerTeamName, *teamToEdit.Description, true, + "owner", unit.AllUnitKeyNames(), nil) // Create team. teamToCreate := &api.CreateTeamOption{ @@ -84,9 +105,9 @@ func TestAPITeam(t *testing.T) { teamID := apiTeam.ID // Edit team. - editDescription := "team 1" - editFalse := false - teamToEdit := &api.EditTeamOption{ + editDescription = "team 1" + editFalse = false + teamToEdit = &api.EditTeamOption{ Name: "teamone", Description: &editDescription, Permission: "admin", @@ -118,7 +139,7 @@ func TestAPITeam(t *testing.T) { teamToEdit.Permission, unit.AllUnitKeyNames(), nil) // Read team. - teamRead := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}) + teamRead := unittest.AssertExistsAndLoadBean(t, &org_model.Team{ID: teamID}) assert.NoError(t, teamRead.LoadUnits(db.DefaultContext)) req = NewRequestf(t, "GET", "/api/v1/teams/%d", teamID). AddTokenAuth(token) @@ -132,7 +153,7 @@ func TestAPITeam(t *testing.T) { req = NewRequestf(t, "DELETE", "/api/v1/teams/%d", teamID). AddTokenAuth(token) MakeRequest(t, req, http.StatusNoContent) - unittest.AssertNotExistsBean(t, &organization.Team{ID: teamID}) + unittest.AssertNotExistsBean(t, &org_model.Team{ID: teamID}) // create team again via UnitsMap // Create team. @@ -189,7 +210,7 @@ func TestAPITeam(t *testing.T) { "read", nil, teamToEdit.UnitsMap) // Read team. - teamRead = unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: teamID}) + teamRead = unittest.AssertExistsAndLoadBean(t, &org_model.Team{ID: teamID}) req = NewRequestf(t, "GET", "/api/v1/teams/%d", teamID). AddTokenAuth(token) resp = MakeRequest(t, req, http.StatusOK) @@ -203,7 +224,7 @@ func TestAPITeam(t *testing.T) { req = NewRequestf(t, "DELETE", "/api/v1/teams/%d", teamID). AddTokenAuth(token) MakeRequest(t, req, http.StatusNoContent) - unittest.AssertNotExistsBean(t, &organization.Team{ID: teamID}) + unittest.AssertNotExistsBean(t, &org_model.Team{ID: teamID}) // Create admin team teamToCreate = &api.CreateTeamOption{ @@ -222,7 +243,7 @@ func TestAPITeam(t *testing.T) { if ut == unit.TypeExternalTracker || ut == unit.TypeExternalWiki { up = perm.AccessModeRead } - unittest.AssertExistsAndLoadBean(t, &organization.TeamUnit{ + unittest.AssertExistsAndLoadBean(t, &org_model.TeamUnit{ OrgID: org.ID, TeamID: apiTeam.ID, Type: ut, @@ -235,7 +256,7 @@ func TestAPITeam(t *testing.T) { req = NewRequestf(t, "DELETE", "/api/v1/teams/%d", teamID). AddTokenAuth(token) MakeRequest(t, req, http.StatusNoContent) - unittest.AssertNotExistsBean(t, &organization.Team{ID: teamID}) + unittest.AssertNotExistsBean(t, &org_model.Team{ID: teamID}) } func checkTeamResponse(t *testing.T, testName string, apiTeam *api.Team, name, description string, includesAllRepositories bool, permission string, units []string, unitsMap map[string]string) { @@ -256,7 +277,7 @@ func checkTeamResponse(t *testing.T, testName string, apiTeam *api.Team, name, d } func checkTeamBean(t *testing.T, id int64, name, description string, includesAllRepositories bool, permission string, units []string, unitsMap map[string]string) { - team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: id}) + team := unittest.AssertExistsAndLoadBean(t, &org_model.Team{ID: id}) assert.NoError(t, team.LoadUnits(db.DefaultContext), "LoadUnits") apiTeam, err := convert.ToTeam(db.DefaultContext, team) assert.NoError(t, err) @@ -299,7 +320,7 @@ func TestAPIGetTeamRepo(t *testing.T) { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 15}) teamRepo := unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 24}) - team := unittest.AssertExistsAndLoadBean(t, &organization.Team{ID: 5}) + team := unittest.AssertExistsAndLoadBean(t, &org_model.Team{ID: 5}) var results api.Repository diff --git a/tests/integration/org_test.go b/tests/integration/org_test.go index ef4ef2bb9b429..1aa634a9d3c4b 100644 --- a/tests/integration/org_test.go +++ b/tests/integration/org_test.go @@ -10,6 +10,9 @@ import ( "testing" auth_model "code.gitea.io/gitea/models/auth" + "code.gitea.io/gitea/models/db" + org_model "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" api "code.gitea.io/gitea/modules/structs" @@ -218,3 +221,39 @@ func TestTeamSearch(t *testing.T) { req = NewRequestf(t, "GET", "/org/%s/teams/-/search?q=%s", org.Name, "team") session.MakeRequest(t, req, http.StatusNotFound) } + +func TestOwnerTeamsEdit(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3, Type: user_model.UserTypeOrganization}) + session := loginUser(t, user1.Name) + + team := unittest.AssertExistsAndLoadBean(t, &org_model.Team{OrgID: org.ID, Name: org_model.OwnerTeamName}) + assert.EqualValues(t, perm.AccessModeOwner, team.AccessMode) + assert.EqualValues(t, "", team.Description) + assert.NoError(t, team.LoadUnits(db.DefaultContext)) + assert.EqualValues(t, 7, len(team.Units)) + for _, unit := range team.Units { + assert.EqualValues(t, perm.AccessModeOwner, unit.AccessMode) + } + + req := NewRequestWithValues(t, "POST", fmt.Sprintf("/org/%s/teams/owners/edit", org.Name), map[string]string{ + "_csrf": GetUserCSRFToken(t, session), + "team_name": "Not Owners", // This change should be ignored + "description": "Just a description", + }) + session.MakeRequest(t, req, http.StatusSeeOther) + + // reload team + team = unittest.AssertExistsAndLoadBean(t, &org_model.Team{ID: team.ID}) + assert.EqualValues(t, perm.AccessModeOwner, team.AccessMode) + assert.EqualValues(t, org_model.OwnerTeamName, team.Name) + assert.EqualValues(t, "Just a description", team.Description) + + assert.NoError(t, team.LoadUnits(db.DefaultContext)) + assert.EqualValues(t, 7, len(team.Units)) + for _, unit := range team.Units { + assert.EqualValues(t, perm.AccessModeOwner, unit.AccessMode) + } +}