Skip to content

Commit 72aa5a2

Browse files
davidsvantessonlafriks
authored andcommitted
Add team option to grant rights for all organization repositories (#8688)
* Add field IsAllRepositories to team * Add AllRepositories to team UI * Manage team with access to all repositories * Add field IsAllRepositories to team API * put backticks around table/column names * rename IsAllRepositories to IncludesAllRepositories * do not reload slice if already loaded * add repo to teams with access to all repositories when changing repo owner * improve tests for teams with access to all repositories * Merge branch 'master' * Change code for adding all repositories Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * fmt after merge * Change code in API EditTeam similar to EditTeamPost web interface Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * Clarify that all repositories will be added Signed-off-by: David Svantesson <davidsvantesson@gmail.com> * All repositories option under Permissions headline * New setting group 'Repository access' * Move check IncludeAllRepositories to removeRepository. * Revert "Move check IncludeAllRepositories to removeRepository." and add comment instead. This reverts commit 753b7d2. * Clarify help text what options do.
1 parent 0109229 commit 72aa5a2

File tree

17 files changed

+382
-75
lines changed

17 files changed

+382
-75
lines changed

integrations/api_team_test.go

+24-16
Original file line numberDiff line numberDiff line change
@@ -55,57 +55,65 @@ func TestAPITeam(t *testing.T) {
5555

5656
// Create team.
5757
teamToCreate := &api.CreateTeamOption{
58-
Name: "team1",
59-
Description: "team one",
60-
Permission: "write",
61-
Units: []string{"repo.code", "repo.issues"},
58+
Name: "team1",
59+
Description: "team one",
60+
IncludesAllRepositories: true,
61+
Permission: "write",
62+
Units: []string{"repo.code", "repo.issues"},
6263
}
6364
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/orgs/%s/teams?token=%s", org.Name, token), teamToCreate)
6465
resp = session.MakeRequest(t, req, http.StatusCreated)
6566
DecodeJSON(t, resp, &apiTeam)
66-
checkTeamResponse(t, &apiTeam, teamToCreate.Name, teamToCreate.Description, teamToCreate.Permission, teamToCreate.Units)
67-
checkTeamBean(t, apiTeam.ID, teamToCreate.Name, teamToCreate.Description, teamToCreate.Permission, teamToCreate.Units)
67+
checkTeamResponse(t, &apiTeam, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories,
68+
teamToCreate.Permission, teamToCreate.Units)
69+
checkTeamBean(t, apiTeam.ID, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories,
70+
teamToCreate.Permission, teamToCreate.Units)
6871
teamID := apiTeam.ID
6972

7073
// Edit team.
7174
teamToEdit := &api.EditTeamOption{
72-
Name: "teamone",
73-
Description: "team 1",
74-
Permission: "admin",
75-
Units: []string{"repo.code", "repo.pulls", "repo.releases"},
75+
Name: "teamone",
76+
Description: "team 1",
77+
IncludesAllRepositories: false,
78+
Permission: "admin",
79+
Units: []string{"repo.code", "repo.pulls", "repo.releases"},
7680
}
7781
req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d?token=%s", teamID, token), teamToEdit)
7882
resp = session.MakeRequest(t, req, http.StatusOK)
7983
DecodeJSON(t, resp, &apiTeam)
80-
checkTeamResponse(t, &apiTeam, teamToEdit.Name, teamToEdit.Description, teamToEdit.Permission, teamToEdit.Units)
81-
checkTeamBean(t, apiTeam.ID, teamToEdit.Name, teamToEdit.Description, teamToEdit.Permission, teamToEdit.Units)
84+
checkTeamResponse(t, &apiTeam, teamToEdit.Name, teamToEdit.Description, teamToEdit.IncludesAllRepositories,
85+
teamToEdit.Permission, teamToEdit.Units)
86+
checkTeamBean(t, apiTeam.ID, teamToEdit.Name, teamToEdit.Description, teamToEdit.IncludesAllRepositories,
87+
teamToEdit.Permission, teamToEdit.Units)
8288

8389
// Read team.
8490
teamRead := models.AssertExistsAndLoadBean(t, &models.Team{ID: teamID}).(*models.Team)
8591
req = NewRequestf(t, "GET", "/api/v1/teams/%d?token="+token, teamID)
8692
resp = session.MakeRequest(t, req, http.StatusOK)
8793
DecodeJSON(t, resp, &apiTeam)
88-
checkTeamResponse(t, &apiTeam, teamRead.Name, teamRead.Description, teamRead.Authorize.String(), teamRead.GetUnitNames())
94+
checkTeamResponse(t, &apiTeam, teamRead.Name, teamRead.Description, teamRead.IncludesAllRepositories,
95+
teamRead.Authorize.String(), teamRead.GetUnitNames())
8996

9097
// Delete team.
9198
req = NewRequestf(t, "DELETE", "/api/v1/teams/%d?token="+token, teamID)
9299
session.MakeRequest(t, req, http.StatusNoContent)
93100
models.AssertNotExistsBean(t, &models.Team{ID: teamID})
94101
}
95102

96-
func checkTeamResponse(t *testing.T, apiTeam *api.Team, name, description string, permission string, units []string) {
103+
func checkTeamResponse(t *testing.T, apiTeam *api.Team, name, description string, includesAllRepositories bool, permission string, units []string) {
97104
assert.Equal(t, name, apiTeam.Name, "name")
98105
assert.Equal(t, description, apiTeam.Description, "description")
106+
assert.Equal(t, includesAllRepositories, apiTeam.IncludesAllRepositories, "includesAllRepositories")
99107
assert.Equal(t, permission, apiTeam.Permission, "permission")
100108
sort.StringSlice(units).Sort()
101109
sort.StringSlice(apiTeam.Units).Sort()
102110
assert.EqualValues(t, units, apiTeam.Units, "units")
103111
}
104112

105-
func checkTeamBean(t *testing.T, id int64, name, description string, permission string, units []string) {
113+
func checkTeamBean(t *testing.T, id int64, name, description string, includesAllRepositories bool, permission string, units []string) {
106114
team := models.AssertExistsAndLoadBean(t, &models.Team{ID: id}).(*models.Team)
107115
assert.NoError(t, team.GetUnits(), "GetUnits")
108-
checkTeamResponse(t, convert.ToTeam(team), name, description, permission, units)
116+
checkTeamResponse(t, convert.ToTeam(team), name, description, includesAllRepositories, permission, units)
109117
}
110118

111119
type TeamSearchResults struct {

models/migrations/migrations.go

+2
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,8 @@ var migrations = []Migration{
264264
NewMigration("Add WhitelistDeployKeys to protected branch", addWhitelistDeployKeysToBranches),
265265
// v104 -> v105
266266
NewMigration("remove unnecessary columns from label", removeLabelUneededCols),
267+
// v105 -> v106
268+
NewMigration("add includes_all_repositories to teams", addTeamIncludesAllRepositories),
267269
}
268270

269271
// Migrate database to current version

models/migrations/v105.go

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright 2019 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package migrations
6+
7+
import (
8+
"xorm.io/xorm"
9+
)
10+
11+
func addTeamIncludesAllRepositories(x *xorm.Engine) error {
12+
13+
type Team struct {
14+
ID int64 `xorm:"pk autoincr"`
15+
IncludesAllRepositories bool `xorm:"NOT NULL DEFAULT false"`
16+
}
17+
18+
if err := x.Sync2(new(Team)); err != nil {
19+
return err
20+
}
21+
22+
_, err := x.Exec("UPDATE `team` SET `includes_all_repositories` = ? WHERE `name`=?",
23+
true, "Owners")
24+
return err
25+
}

models/org.go

+9-5
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ func (org *User) GetOwnerTeam() (*Team, error) {
4848
}
4949

5050
func (org *User) getTeams(e Engine) error {
51+
if org.Teams != nil {
52+
return nil
53+
}
5154
return e.
5255
Where("org_id=?", org.ID).
5356
OrderBy("CASE WHEN name LIKE '" + ownerTeamName + "' THEN '' ELSE name END").
@@ -149,11 +152,12 @@ func CreateOrganization(org, owner *User) (err error) {
149152

150153
// Create default owner team.
151154
t := &Team{
152-
OrgID: org.ID,
153-
LowerName: strings.ToLower(ownerTeamName),
154-
Name: ownerTeamName,
155-
Authorize: AccessModeOwner,
156-
NumMembers: 1,
155+
OrgID: org.ID,
156+
LowerName: strings.ToLower(ownerTeamName),
157+
Name: ownerTeamName,
158+
Authorize: AccessModeOwner,
159+
NumMembers: 1,
160+
IncludesAllRepositories: true,
157161
}
158162
if _, err = sess.Insert(t); err != nil {
159163
return fmt.Errorf("insert owner team: %v", err)

models/org_team.go

+58-12
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,18 @@ const ownerTeamName = "Owners"
2222

2323
// Team represents a organization team.
2424
type Team struct {
25-
ID int64 `xorm:"pk autoincr"`
26-
OrgID int64 `xorm:"INDEX"`
27-
LowerName string
28-
Name string
29-
Description string
30-
Authorize AccessMode
31-
Repos []*Repository `xorm:"-"`
32-
Members []*User `xorm:"-"`
33-
NumRepos int
34-
NumMembers int
35-
Units []*TeamUnit `xorm:"-"`
25+
ID int64 `xorm:"pk autoincr"`
26+
OrgID int64 `xorm:"INDEX"`
27+
LowerName string
28+
Name string
29+
Description string
30+
Authorize AccessMode
31+
Repos []*Repository `xorm:"-"`
32+
Members []*User `xorm:"-"`
33+
NumRepos int
34+
NumMembers int
35+
Units []*TeamUnit `xorm:"-"`
36+
IncludesAllRepositories bool `xorm:"NOT NULL DEFAULT false"`
3637
}
3738

3839
// SearchTeamOptions holds the search options
@@ -149,6 +150,9 @@ func (t *Team) IsMember(userID int64) bool {
149150
}
150151

151152
func (t *Team) getRepositories(e Engine) error {
153+
if t.Repos != nil {
154+
return nil
155+
}
152156
return e.Join("INNER", "team_repo", "repository.id = team_repo.repo_id").
153157
Where("team_repo.team_id=?", t.ID).
154158
OrderBy("repository.name").
@@ -220,6 +224,25 @@ func (t *Team) addRepository(e Engine, repo *Repository) (err error) {
220224
return nil
221225
}
222226

227+
// addAllRepositories adds all repositories to the team.
228+
// If the team already has some repositories they will be left unchanged.
229+
func (t *Team) addAllRepositories(e Engine) error {
230+
var orgRepos []Repository
231+
if err := e.Where("owner_id = ?", t.OrgID).Find(&orgRepos); err != nil {
232+
return fmt.Errorf("get org repos: %v", err)
233+
}
234+
235+
for _, repo := range orgRepos {
236+
if !t.hasRepository(e, repo.ID) {
237+
if err := t.addRepository(e, &repo); err != nil {
238+
return fmt.Errorf("addRepository: %v", err)
239+
}
240+
}
241+
}
242+
243+
return nil
244+
}
245+
223246
// AddRepository adds new repository to team of organization.
224247
func (t *Team) AddRepository(repo *Repository) (err error) {
225248
if repo.OwnerID != t.OrgID {
@@ -241,6 +264,8 @@ func (t *Team) AddRepository(repo *Repository) (err error) {
241264
return sess.Commit()
242265
}
243266

267+
// removeRepository removes a repository from a team and recalculates access
268+
// Note: Repository shall not be removed from team if it includes all repositories (unless the repository is deleted)
244269
func (t *Team) removeRepository(e Engine, repo *Repository, recalculate bool) (err error) {
245270
if err = removeTeamRepo(e, t.ID, repo.ID); err != nil {
246271
return err
@@ -284,11 +309,16 @@ func (t *Team) removeRepository(e Engine, repo *Repository, recalculate bool) (e
284309
}
285310

286311
// RemoveRepository removes repository from team of organization.
312+
// If the team shall include all repositories the request is ignored.
287313
func (t *Team) RemoveRepository(repoID int64) error {
288314
if !t.HasRepository(repoID) {
289315
return nil
290316
}
291317

318+
if t.IncludesAllRepositories {
319+
return nil
320+
}
321+
292322
repo, err := GetRepositoryByID(repoID)
293323
if err != nil {
294324
return err
@@ -394,6 +424,14 @@ func NewTeam(t *Team) (err error) {
394424
}
395425
}
396426

427+
// Add all repositories to the team if it has access to all of them.
428+
if t.IncludesAllRepositories {
429+
err = t.addAllRepositories(sess)
430+
if err != nil {
431+
return fmt.Errorf("addAllRepositories: %v", err)
432+
}
433+
}
434+
397435
// Update organization number of teams.
398436
if _, err = sess.Exec("UPDATE `user` SET num_teams=num_teams+1 WHERE id = ?", t.OrgID); err != nil {
399437
errRollback := sess.Rollback()
@@ -446,7 +484,7 @@ func GetTeamByID(teamID int64) (*Team, error) {
446484
}
447485

448486
// UpdateTeam updates information of team.
449-
func UpdateTeam(t *Team, authChanged bool) (err error) {
487+
func UpdateTeam(t *Team, authChanged bool, includeAllChanged bool) (err error) {
450488
if len(t.Name) == 0 {
451489
return errors.New("empty team name")
452490
}
@@ -511,6 +549,14 @@ func UpdateTeam(t *Team, authChanged bool) (err error) {
511549
}
512550
}
513551

552+
// Add all repositories to the team if it has access to all of them.
553+
if includeAllChanged && t.IncludesAllRepositories {
554+
err = t.addAllRepositories(sess)
555+
if err != nil {
556+
return fmt.Errorf("addAllRepositories: %v", err)
557+
}
558+
}
559+
514560
return sess.Commit()
515561
}
516562

0 commit comments

Comments
 (0)