Skip to content

Commit

Permalink
Fix admin team access mode value in team_unit table (#24012)
Browse files Browse the repository at this point in the history
Same as #23675
Feedback:
#23879 (comment)
  • Loading branch information
yp05327 authored Apr 13, 2023
1 parent 469dc44 commit b7221be
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 45 deletions.
2 changes: 2 additions & 0 deletions models/migrations/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,8 @@ var migrations = []Migration{
NewMigration("Change Container Metadata", v1_20.ChangeContainerMetadataMultiArch),
// v251 -> v252
NewMigration("Fix incorrect owner team unit access mode", v1_20.FixIncorrectOwnerTeamUnitAccessMode),
// v252 -> v253
NewMigration("Fix incorrect admin team unit access mode", v1_20.FixIncorrectAdminTeamUnitAccessMode),
}

// GetCurrentDBVersion returns the current db version
Expand Down
47 changes: 47 additions & 0 deletions models/migrations/v1_20/v252.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package v1_20 //nolint

import (
"code.gitea.io/gitea/modules/log"

"xorm.io/xorm"
)

func FixIncorrectAdminTeamUnitAccessMode(x *xorm.Engine) error {
type UnitType int
type AccessMode int

type TeamUnit struct {
ID int64 `xorm:"pk autoincr"`
OrgID int64 `xorm:"INDEX"`
TeamID int64 `xorm:"UNIQUE(s)"`
Type UnitType `xorm:"UNIQUE(s)"`
AccessMode AccessMode
}

const (
// AccessModeAdmin admin access
AccessModeAdmin = 3
)

sess := x.NewSession()
defer sess.Close()

if err := sess.Begin(); err != nil {
return err
}

count, err := sess.Table("team_unit").
Where("team_id IN (SELECT id FROM team WHERE authorize = ?)", AccessModeAdmin).
Update(&TeamUnit{
AccessMode: AccessModeAdmin,
})
if err != nil {
return err
}
log.Debug("Updated %d admin team unit access mode to belong to admin instead of none", count)

return sess.Commit()
}
19 changes: 19 additions & 0 deletions routers/api/v1/org/team.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,21 @@ func attachTeamUnitsMap(team *organization.Team, unitsMap map[string]string) {
}
}

func attachAdminTeamUnits(team *organization.Team) {
team.Units = make([]*organization.TeamUnit, 0, len(unit_model.AllRepoUnitTypes))
for _, ut := range unit_model.AllRepoUnitTypes {
up := perm.AccessModeAdmin
if ut == unit_model.TypeExternalTracker || ut == unit_model.TypeExternalWiki {
up = perm.AccessModeRead
}
team.Units = append(team.Units, &organization.TeamUnit{
OrgID: team.OrgID,
Type: ut,
AccessMode: up,
})
}
}

// CreateTeam api for create a team
func CreateTeam(ctx *context.APIContext) {
// swagger:operation POST /orgs/{org}/teams organization orgCreateTeam
Expand Down Expand Up @@ -213,6 +228,8 @@ func CreateTeam(ctx *context.APIContext) {
ctx.Error(http.StatusInternalServerError, "getTeamUnits", errors.New("units permission should not be empty"))
return
}
} else {
attachAdminTeamUnits(team)
}

if err := models.NewTeam(team); err != nil {
Expand Down Expand Up @@ -300,6 +317,8 @@ func EditTeam(ctx *context.APIContext) {
} else if len(form.Units) > 0 {
attachTeamUnits(team, form.Units)
}
} else {
attachAdminTeamUnits(team)
}

if err := models.UpdateTeam(team, isAuthChanged, isIncludeAllChanged); err != nil {
Expand Down
93 changes: 50 additions & 43 deletions routers/web/org/teams.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package org

import (
"fmt"
"net/http"
"net/url"
"path"
Expand Down Expand Up @@ -264,14 +265,26 @@ func NewTeam(ctx *context.Context) {
ctx.HTML(http.StatusOK, tplTeamNew)
}

func getUnitPerms(forms url.Values) map[unit_model.Type]perm.AccessMode {
func getUnitPerms(forms url.Values, teamPermission perm.AccessMode) map[unit_model.Type]perm.AccessMode {
unitPerms := make(map[unit_model.Type]perm.AccessMode)
for k, v := range forms {
if strings.HasPrefix(k, "unit_") {
t, _ := strconv.Atoi(k[5:])
if t > 0 {
vv, _ := strconv.Atoi(v[0])
unitPerms[unit_model.Type(t)] = perm.AccessMode(vv)
for _, ut := range unit_model.AllRepoUnitTypes {
// Default accessmode is none
unitPerms[ut] = perm.AccessModeNone

v, ok := forms[fmt.Sprintf("unit_%d", ut)]
if ok {
vv, _ := strconv.Atoi(v[0])
if teamPermission >= perm.AccessModeAdmin {
unitPerms[ut] = teamPermission
// Don't allow `TypeExternal{Tracker,Wiki}` to influence this as they can only be set to READ perms.
if ut == unit_model.TypeExternalTracker || ut == unit_model.TypeExternalWiki {
unitPerms[ut] = perm.AccessModeRead
}
} else {
unitPerms[ut] = perm.AccessMode(vv)
if unitPerms[ut] >= perm.AccessModeAdmin {
unitPerms[ut] = perm.AccessModeWrite
}
}
}
}
Expand All @@ -282,8 +295,8 @@ func getUnitPerms(forms url.Values) map[unit_model.Type]perm.AccessMode {
func NewTeamPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.CreateTeamForm)
includesAllRepositories := form.RepoAccess == "all"
unitPerms := getUnitPerms(ctx.Req.Form)
p := perm.ParseAccessMode(form.Permission)
unitPerms := getUnitPerms(ctx.Req.Form, p)
if p < perm.AccessModeAdmin {
// if p is less than admin accessmode, then it should be general accessmode,
// so we should calculate the minial accessmode from units accessmodes.
Expand All @@ -299,17 +312,15 @@ func NewTeamPost(ctx *context.Context) {
CanCreateOrgRepo: form.CanCreateOrgRepo,
}

if t.AccessMode < perm.AccessModeAdmin {
units := make([]*org_model.TeamUnit, 0, len(unitPerms))
for tp, perm := range unitPerms {
units = append(units, &org_model.TeamUnit{
OrgID: ctx.Org.Organization.ID,
Type: tp,
AccessMode: perm,
})
}
t.Units = units
units := make([]*org_model.TeamUnit, 0, len(unitPerms))
for tp, perm := range unitPerms {
units = append(units, &org_model.TeamUnit{
OrgID: ctx.Org.Organization.ID,
Type: tp,
AccessMode: perm,
})
}
t.Units = units

ctx.Data["Title"] = ctx.Org.Organization.FullName
ctx.Data["PageIsOrgTeams"] = true
Expand Down Expand Up @@ -422,8 +433,11 @@ func SearchTeam(ctx *context.Context) {
func EditTeam(ctx *context.Context) {
ctx.Data["Title"] = ctx.Org.Organization.FullName
ctx.Data["PageIsOrgTeams"] = true
ctx.Data["team_name"] = ctx.Org.Team.Name
ctx.Data["desc"] = ctx.Org.Team.Description
if err := ctx.Org.Team.LoadUnits(ctx); err != nil {
ctx.ServerError("LoadUnits", err)
return
}
ctx.Data["Team"] = ctx.Org.Team
ctx.Data["Units"] = unit_model.Units
ctx.HTML(http.StatusOK, tplTeamNew)
}
Expand All @@ -432,7 +446,13 @@ func EditTeam(ctx *context.Context) {
func EditTeamPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.CreateTeamForm)
t := ctx.Org.Team
unitPerms := getUnitPerms(ctx.Req.Form)
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"
Expand All @@ -443,14 +463,6 @@ func EditTeamPost(ctx *context.Context) {
ctx.Data["Units"] = unit_model.Units

if !t.IsOwnerTeam() {
// Validate permission level.
newAccessMode := perm.ParseAccessMode(form.Permission)
if newAccessMode < perm.AccessModeAdmin {
// if p 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)
}

t.Name = form.TeamName
if t.AccessMode != newAccessMode {
isAuthChanged = true
Expand All @@ -467,21 +479,16 @@ func EditTeamPost(ctx *context.Context) {
}

t.Description = form.Description
if t.AccessMode < perm.AccessModeAdmin {
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,
})
}
if err := org_model.UpdateTeamUnits(t, units); err != nil {
ctx.Error(http.StatusInternalServerError, "UpdateTeamUnits", err.Error())
return
}
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)
Expand Down
4 changes: 2 additions & 2 deletions templates/org/team/new.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@
</td>
<td class="center aligned">
<div class="ui radio checkbox">
<input type="radio" name="unit_{{$unit.Type.Value}}" value="2"{{if (eq ($.Team.UnitAccessMode $.Context $unit.Type) 2)}} checked{{end}} {{if $unit.Type.UnitGlobalDisabled}}disabled{{end}} title="{{$.locale.Tr "org.teams.write_access"}}">
<input type="radio" name="unit_{{$unit.Type.Value}}" value="2"{{if (ge ($.Team.UnitAccessMode $.Context $unit.Type) 2)}} checked{{end}} {{if $unit.Type.UnitGlobalDisabled}}disabled{{end}} title="{{$.locale.Tr "org.teams.write_access"}}">
</div>
</td>
</tr>
Expand Down Expand Up @@ -137,7 +137,7 @@
{{else}}
<button class="ui green button">{{.locale.Tr "org.teams.update_settings"}}</button>
{{if not (eq .Team.LowerName "owners")}}
<button class="ui red button delete-button" data-url="{{.OrgLink}}/teams/{{.team_name | PathEscape}}/delete">{{.locale.Tr "org.teams.delete_team"}}</button>
<button class="ui red button delete-button" data-url="{{.OrgLink}}/teams/{{.Team.Name | PathEscape}}/delete">{{.locale.Tr "org.teams.delete_team"}}</button>
{{end}}
{{end}}
</div>
Expand Down
31 changes: 31 additions & 0 deletions tests/integration/api_team_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/perm"
"code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/models/unittest"
Expand Down Expand Up @@ -189,6 +190,36 @@ func TestAPITeam(t *testing.T) {
req = NewRequestf(t, "DELETE", "/api/v1/teams/%d?token="+token, teamID)
MakeRequest(t, req, http.StatusNoContent)
unittest.AssertNotExistsBean(t, &organization.Team{ID: teamID})

// Create admin team
teamToCreate = &api.CreateTeamOption{
Name: "teamadmin",
Description: "team admin",
IncludesAllRepositories: true,
Permission: "admin",
}
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/orgs/%s/teams?token=%s", org.Name, token), teamToCreate)
resp = MakeRequest(t, req, http.StatusCreated)
apiTeam = api.Team{}
DecodeJSON(t, resp, &apiTeam)
for _, ut := range unit.AllRepoUnitTypes {
up := perm.AccessModeAdmin
if ut == unit.TypeExternalTracker || ut == unit.TypeExternalWiki {
up = perm.AccessModeRead
}
unittest.AssertExistsAndLoadBean(t, &organization.TeamUnit{
OrgID: org.ID,
TeamID: apiTeam.ID,
Type: ut,
AccessMode: up,
})
}
teamID = apiTeam.ID

// Delete team.
req = NewRequestf(t, "DELETE", "/api/v1/teams/%d?token="+token, teamID)
MakeRequest(t, req, http.StatusNoContent)
unittest.AssertNotExistsBean(t, &organization.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) {
Expand Down

0 comments on commit b7221be

Please sign in to comment.