Fix admin team access mode value in team_unit table (#24012)

Same as https://github.com/go-gitea/gitea/pull/23675
Feedback:
https://github.com/go-gitea/gitea/pull/23879#issuecomment-1500923636
This commit is contained in:
yp05327 2023-04-14 04:06:10 +09:00 committed by GitHub
parent 469dc4459b
commit b7221bec34
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 151 additions and 45 deletions

View file

@ -481,6 +481,8 @@ var migrations = []Migration{
NewMigration("Change Container Metadata", v1_20.ChangeContainerMetadataMultiArch), NewMigration("Change Container Metadata", v1_20.ChangeContainerMetadataMultiArch),
// v251 -> v252 // v251 -> v252
NewMigration("Fix incorrect owner team unit access mode", v1_20.FixIncorrectOwnerTeamUnitAccessMode), 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 // GetCurrentDBVersion returns the current db version

View file

@ -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()
}

View file

@ -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 // CreateTeam api for create a team
func CreateTeam(ctx *context.APIContext) { func CreateTeam(ctx *context.APIContext) {
// swagger:operation POST /orgs/{org}/teams organization orgCreateTeam // swagger:operation POST /orgs/{org}/teams organization orgCreateTeam
@ -213,6 +228,8 @@ func CreateTeam(ctx *context.APIContext) {
ctx.Error(http.StatusInternalServerError, "getTeamUnits", errors.New("units permission should not be empty")) ctx.Error(http.StatusInternalServerError, "getTeamUnits", errors.New("units permission should not be empty"))
return return
} }
} else {
attachAdminTeamUnits(team)
} }
if err := models.NewTeam(team); err != nil { if err := models.NewTeam(team); err != nil {
@ -300,6 +317,8 @@ func EditTeam(ctx *context.APIContext) {
} else if len(form.Units) > 0 { } else if len(form.Units) > 0 {
attachTeamUnits(team, form.Units) attachTeamUnits(team, form.Units)
} }
} else {
attachAdminTeamUnits(team)
} }
if err := models.UpdateTeam(team, isAuthChanged, isIncludeAllChanged); err != nil { if err := models.UpdateTeam(team, isAuthChanged, isIncludeAllChanged); err != nil {

View file

@ -5,6 +5,7 @@
package org package org
import ( import (
"fmt"
"net/http" "net/http"
"net/url" "net/url"
"path" "path"
@ -264,14 +265,26 @@ func NewTeam(ctx *context.Context) {
ctx.HTML(http.StatusOK, tplTeamNew) 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) unitPerms := make(map[unit_model.Type]perm.AccessMode)
for k, v := range forms { for _, ut := range unit_model.AllRepoUnitTypes {
if strings.HasPrefix(k, "unit_") { // Default accessmode is none
t, _ := strconv.Atoi(k[5:]) unitPerms[ut] = perm.AccessModeNone
if t > 0 {
v, ok := forms[fmt.Sprintf("unit_%d", ut)]
if ok {
vv, _ := strconv.Atoi(v[0]) vv, _ := strconv.Atoi(v[0])
unitPerms[unit_model.Type(t)] = perm.AccessMode(vv) 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
}
} }
} }
} }
@ -282,8 +295,8 @@ func getUnitPerms(forms url.Values) map[unit_model.Type]perm.AccessMode {
func NewTeamPost(ctx *context.Context) { func NewTeamPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.CreateTeamForm) form := web.GetForm(ctx).(*forms.CreateTeamForm)
includesAllRepositories := form.RepoAccess == "all" includesAllRepositories := form.RepoAccess == "all"
unitPerms := getUnitPerms(ctx.Req.Form)
p := perm.ParseAccessMode(form.Permission) p := perm.ParseAccessMode(form.Permission)
unitPerms := getUnitPerms(ctx.Req.Form, p)
if p < perm.AccessModeAdmin { if p < perm.AccessModeAdmin {
// if p is less than admin accessmode, then it should be general accessmode, // if p is less than admin accessmode, then it should be general accessmode,
// so we should calculate the minial accessmode from units accessmodes. // so we should calculate the minial accessmode from units accessmodes.
@ -299,7 +312,6 @@ func NewTeamPost(ctx *context.Context) {
CanCreateOrgRepo: form.CanCreateOrgRepo, CanCreateOrgRepo: form.CanCreateOrgRepo,
} }
if t.AccessMode < perm.AccessModeAdmin {
units := make([]*org_model.TeamUnit, 0, len(unitPerms)) units := make([]*org_model.TeamUnit, 0, len(unitPerms))
for tp, perm := range unitPerms { for tp, perm := range unitPerms {
units = append(units, &org_model.TeamUnit{ units = append(units, &org_model.TeamUnit{
@ -309,7 +321,6 @@ func NewTeamPost(ctx *context.Context) {
}) })
} }
t.Units = units t.Units = units
}
ctx.Data["Title"] = ctx.Org.Organization.FullName ctx.Data["Title"] = ctx.Org.Organization.FullName
ctx.Data["PageIsOrgTeams"] = true ctx.Data["PageIsOrgTeams"] = true
@ -422,8 +433,11 @@ func SearchTeam(ctx *context.Context) {
func EditTeam(ctx *context.Context) { func EditTeam(ctx *context.Context) {
ctx.Data["Title"] = ctx.Org.Organization.FullName ctx.Data["Title"] = ctx.Org.Organization.FullName
ctx.Data["PageIsOrgTeams"] = true ctx.Data["PageIsOrgTeams"] = true
ctx.Data["team_name"] = ctx.Org.Team.Name if err := ctx.Org.Team.LoadUnits(ctx); err != nil {
ctx.Data["desc"] = ctx.Org.Team.Description ctx.ServerError("LoadUnits", err)
return
}
ctx.Data["Team"] = ctx.Org.Team
ctx.Data["Units"] = unit_model.Units ctx.Data["Units"] = unit_model.Units
ctx.HTML(http.StatusOK, tplTeamNew) ctx.HTML(http.StatusOK, tplTeamNew)
} }
@ -432,7 +446,13 @@ func EditTeam(ctx *context.Context) {
func EditTeamPost(ctx *context.Context) { func EditTeamPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.CreateTeamForm) form := web.GetForm(ctx).(*forms.CreateTeamForm)
t := ctx.Org.Team 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 isAuthChanged := false
isIncludeAllChanged := false isIncludeAllChanged := false
includesAllRepositories := form.RepoAccess == "all" includesAllRepositories := form.RepoAccess == "all"
@ -443,14 +463,6 @@ func EditTeamPost(ctx *context.Context) {
ctx.Data["Units"] = unit_model.Units ctx.Data["Units"] = unit_model.Units
if !t.IsOwnerTeam() { 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 t.Name = form.TeamName
if t.AccessMode != newAccessMode { if t.AccessMode != newAccessMode {
isAuthChanged = true isAuthChanged = true
@ -467,21 +479,16 @@ func EditTeamPost(ctx *context.Context) {
} }
t.Description = form.Description t.Description = form.Description
if t.AccessMode < perm.AccessModeAdmin { units := make([]*org_model.TeamUnit, 0, len(unitPerms))
units := make([]org_model.TeamUnit, 0, len(unitPerms))
for tp, perm := range unitPerms { for tp, perm := range unitPerms {
units = append(units, org_model.TeamUnit{ units = append(units, &org_model.TeamUnit{
OrgID: t.OrgID, OrgID: t.OrgID,
TeamID: t.ID, TeamID: t.ID,
Type: tp, Type: tp,
AccessMode: perm, AccessMode: perm,
}) })
} }
if err := org_model.UpdateTeamUnits(t, units); err != nil { t.Units = units
ctx.Error(http.StatusInternalServerError, "UpdateTeamUnits", err.Error())
return
}
}
if ctx.HasError() { if ctx.HasError() {
ctx.HTML(http.StatusOK, tplTeamNew) ctx.HTML(http.StatusOK, tplTeamNew)

View file

@ -109,7 +109,7 @@
</td> </td>
<td class="center aligned"> <td class="center aligned">
<div class="ui radio checkbox"> <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> </div>
</td> </td>
</tr> </tr>
@ -137,7 +137,7 @@
{{else}} {{else}}
<button class="ui green button">{{.locale.Tr "org.teams.update_settings"}}</button> <button class="ui green button">{{.locale.Tr "org.teams.update_settings"}}</button>
{{if not (eq .Team.LowerName "owners")}} {{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}}
{{end}} {{end}}
</div> </div>

View file

@ -12,6 +12,7 @@ import (
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"
"code.gitea.io/gitea/models/perm"
"code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
@ -189,6 +190,36 @@ func TestAPITeam(t *testing.T) {
req = NewRequestf(t, "DELETE", "/api/v1/teams/%d?token="+token, teamID) req = NewRequestf(t, "DELETE", "/api/v1/teams/%d?token="+token, teamID)
MakeRequest(t, req, http.StatusNoContent) MakeRequest(t, req, http.StatusNoContent)
unittest.AssertNotExistsBean(t, &organization.Team{ID: teamID}) 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) { func checkTeamResponse(t *testing.T, testName string, apiTeam *api.Team, name, description string, includesAllRepositories bool, permission string, units []string, unitsMap map[string]string) {