Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add team option to grant rights for all organization repositories #8688

Merged
merged 27 commits into from
Nov 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
0b8e90e
Add field IsAllRepositories to team
ngourdon Apr 28, 2019
b478a10
Add AllRepositories to team UI
ngourdon Apr 28, 2019
e5f6d9e
Manage team with access to all repositories
ngourdon Apr 28, 2019
7cca125
Add field IsAllRepositories to team API
ngourdon Apr 28, 2019
a02aa18
put backticks around table/column names
ngourdon Apr 30, 2019
8216a2a
rename IsAllRepositories to IncludesAllRepositories
ngourdon Apr 30, 2019
f698385
do not reload slice if already loaded
ngourdon Apr 30, 2019
0496588
add repo to teams with access to all repositories when changing repo …
ngourdon May 8, 2019
c097a29
improve tests for teams with access to all repositories
ngourdon May 8, 2019
3a3ccf8
Merge branch 'master'
ngourdon May 8, 2019
5fa75d4
Merge branch 'master'
ngourdon May 9, 2019
d262016
Merge branch 'master' into fix-6409
ngourdon May 12, 2019
8f297ca
Merge branch 'master' into fix-6409
ngourdon Jul 11, 2019
550cbcc
Change code for adding all repositories
davidsvantesson Oct 5, 2019
5316bdf
Merge remote-tracking branch 'upstream/master' into team-grant-all-repos
davidsvantesson Oct 5, 2019
c1a2e26
fmt after merge
davidsvantesson Oct 5, 2019
cfeb1c3
Change code in API EditTeam similar to EditTeamPost web interface
davidsvantesson Oct 5, 2019
06ac307
Clarify that all repositories will be added
davidsvantesson Oct 5, 2019
3f291df
Merge remote-tracking branch 'upstream/master' into team-grant-all-repos
davidsvantesson Oct 25, 2019
5f6a701
All repositories option under Permissions headline
davidsvantesson Oct 25, 2019
c4546cd
New setting group 'Repository access'
davidsvantesson Oct 26, 2019
ee52d2d
Merge remote-tracking branch 'upstream/master' into team-grant-all-re…
davidsvantesson Oct 26, 2019
753b7d2
Move check IncludeAllRepositories to removeRepository.
davidsvantesson Nov 2, 2019
7c7fa0d
Revert "Move check IncludeAllRepositories to removeRepository." and a…
davidsvantesson Nov 2, 2019
30faed9
Merge branch 'master' into team-grant-all-repos
davidsvantesson Nov 2, 2019
ad2d925
Clarify help text what options do.
davidsvantesson Nov 3, 2019
4a509e8
Merge branch 'master' into team-grant-all-repos
lafriks Nov 6, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 24 additions & 16 deletions integrations/api_team_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,57 +55,65 @@ func TestAPITeam(t *testing.T) {

// Create team.
teamToCreate := &api.CreateTeamOption{
Name: "team1",
Description: "team one",
Permission: "write",
Units: []string{"repo.code", "repo.issues"},
Name: "team1",
Description: "team one",
IncludesAllRepositories: true,
Permission: "write",
Units: []string{"repo.code", "repo.issues"},
}
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/orgs/%s/teams?token=%s", org.Name, token), teamToCreate)
resp = session.MakeRequest(t, req, http.StatusCreated)
DecodeJSON(t, resp, &apiTeam)
checkTeamResponse(t, &apiTeam, teamToCreate.Name, teamToCreate.Description, teamToCreate.Permission, teamToCreate.Units)
checkTeamBean(t, apiTeam.ID, teamToCreate.Name, teamToCreate.Description, teamToCreate.Permission, teamToCreate.Units)
checkTeamResponse(t, &apiTeam, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories,
teamToCreate.Permission, teamToCreate.Units)
checkTeamBean(t, apiTeam.ID, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories,
teamToCreate.Permission, teamToCreate.Units)
teamID := apiTeam.ID

// Edit team.
teamToEdit := &api.EditTeamOption{
Name: "teamone",
Description: "team 1",
Permission: "admin",
Units: []string{"repo.code", "repo.pulls", "repo.releases"},
Name: "teamone",
Description: "team 1",
IncludesAllRepositories: false,
Permission: "admin",
Units: []string{"repo.code", "repo.pulls", "repo.releases"},
}
req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d?token=%s", teamID, token), teamToEdit)
resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &apiTeam)
checkTeamResponse(t, &apiTeam, teamToEdit.Name, teamToEdit.Description, teamToEdit.Permission, teamToEdit.Units)
checkTeamBean(t, apiTeam.ID, teamToEdit.Name, teamToEdit.Description, teamToEdit.Permission, teamToEdit.Units)
checkTeamResponse(t, &apiTeam, teamToEdit.Name, teamToEdit.Description, teamToEdit.IncludesAllRepositories,
teamToEdit.Permission, teamToEdit.Units)
checkTeamBean(t, apiTeam.ID, teamToEdit.Name, teamToEdit.Description, teamToEdit.IncludesAllRepositories,
teamToEdit.Permission, teamToEdit.Units)

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

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

func checkTeamResponse(t *testing.T, apiTeam *api.Team, name, description string, permission string, units []string) {
func checkTeamResponse(t *testing.T, apiTeam *api.Team, name, description string, includesAllRepositories bool, permission string, units []string) {
assert.Equal(t, name, apiTeam.Name, "name")
assert.Equal(t, description, apiTeam.Description, "description")
assert.Equal(t, includesAllRepositories, apiTeam.IncludesAllRepositories, "includesAllRepositories")
assert.Equal(t, permission, apiTeam.Permission, "permission")
sort.StringSlice(units).Sort()
sort.StringSlice(apiTeam.Units).Sort()
assert.EqualValues(t, units, apiTeam.Units, "units")
}

func checkTeamBean(t *testing.T, id int64, name, description string, permission string, units []string) {
func checkTeamBean(t *testing.T, id int64, name, description string, includesAllRepositories bool, permission string, units []string) {
team := models.AssertExistsAndLoadBean(t, &models.Team{ID: id}).(*models.Team)
assert.NoError(t, team.GetUnits(), "GetUnits")
checkTeamResponse(t, convert.ToTeam(team), name, description, permission, units)
checkTeamResponse(t, convert.ToTeam(team), name, description, includesAllRepositories, permission, units)
}

type TeamSearchResults struct {
Expand Down
2 changes: 2 additions & 0 deletions models/migrations/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,8 @@ var migrations = []Migration{
NewMigration("Add WhitelistDeployKeys to protected branch", addWhitelistDeployKeysToBranches),
// v104 -> v105
NewMigration("remove unnecessary columns from label", removeLabelUneededCols),
// v105 -> v106
NewMigration("add includes_all_repositories to teams", addTeamIncludesAllRepositories),
}

// Migrate database to current version
Expand Down
25 changes: 25 additions & 0 deletions models/migrations/v105.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package migrations

import (
"xorm.io/xorm"
)

func addTeamIncludesAllRepositories(x *xorm.Engine) error {

type Team struct {
ID int64 `xorm:"pk autoincr"`
IncludesAllRepositories bool `xorm:"NOT NULL DEFAULT false"`
}

if err := x.Sync2(new(Team)); err != nil {
return err
}

_, err := x.Exec("UPDATE `team` SET `includes_all_repositories` = ? WHERE `name`=?",
true, "Owners")
return err
}
14 changes: 9 additions & 5 deletions models/org.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ func (org *User) GetOwnerTeam() (*Team, error) {
}

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

// Create default owner team.
t := &Team{
OrgID: org.ID,
LowerName: strings.ToLower(ownerTeamName),
Name: ownerTeamName,
Authorize: AccessModeOwner,
NumMembers: 1,
OrgID: org.ID,
LowerName: strings.ToLower(ownerTeamName),
Name: ownerTeamName,
Authorize: AccessModeOwner,
NumMembers: 1,
IncludesAllRepositories: true,
}
if _, err = sess.Insert(t); err != nil {
return fmt.Errorf("insert owner team: %v", err)
Expand Down
70 changes: 58 additions & 12 deletions models/org_team.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,18 @@ const ownerTeamName = "Owners"

// Team represents a organization team.
type Team struct {
ID int64 `xorm:"pk autoincr"`
OrgID int64 `xorm:"INDEX"`
LowerName string
Name string
Description string
Authorize AccessMode
Repos []*Repository `xorm:"-"`
Members []*User `xorm:"-"`
NumRepos int
NumMembers int
Units []*TeamUnit `xorm:"-"`
ID int64 `xorm:"pk autoincr"`
OrgID int64 `xorm:"INDEX"`
LowerName string
Name string
Description string
Authorize AccessMode
Repos []*Repository `xorm:"-"`
Members []*User `xorm:"-"`
NumRepos int
NumMembers int
Units []*TeamUnit `xorm:"-"`
IncludesAllRepositories bool `xorm:"NOT NULL DEFAULT false"`
}

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

func (t *Team) getRepositories(e Engine) error {
if t.Repos != nil {
return nil
}
return e.Join("INNER", "team_repo", "repository.id = team_repo.repo_id").
Where("team_repo.team_id=?", t.ID).
OrderBy("repository.name").
Expand Down Expand Up @@ -220,6 +224,25 @@ func (t *Team) addRepository(e Engine, repo *Repository) (err error) {
return nil
}

// addAllRepositories adds all repositories to the team.
// If the team already has some repositories they will be left unchanged.
func (t *Team) addAllRepositories(e Engine) error {
var orgRepos []Repository
if err := e.Where("owner_id = ?", t.OrgID).Find(&orgRepos); err != nil {
return fmt.Errorf("get org repos: %v", err)
}

for _, repo := range orgRepos {
lafriks marked this conversation as resolved.
Show resolved Hide resolved
if !t.hasRepository(e, repo.ID) {
if err := t.addRepository(e, &repo); err != nil {
return fmt.Errorf("addRepository: %v", err)
}
}
}

return nil
}

// AddRepository adds new repository to team of organization.
func (t *Team) AddRepository(repo *Repository) (err error) {
if repo.OwnerID != t.OrgID {
Expand All @@ -241,6 +264,8 @@ func (t *Team) AddRepository(repo *Repository) (err error) {
return sess.Commit()
}

// removeRepository 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 (t *Team) removeRepository(e Engine, repo *Repository, recalculate bool) (err error) {
if err = removeTeamRepo(e, t.ID, repo.ID); err != nil {
return err
Expand Down Expand Up @@ -284,11 +309,16 @@ func (t *Team) removeRepository(e Engine, repo *Repository, recalculate bool) (e
}

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

if t.IncludesAllRepositories {
lafriks marked this conversation as resolved.
Show resolved Hide resolved
return nil
}

repo, err := GetRepositoryByID(repoID)
if err != nil {
return err
Expand Down Expand Up @@ -394,6 +424,14 @@ func NewTeam(t *Team) (err error) {
}
}

// Add all repositories to the team if it has access to all of them.
if t.IncludesAllRepositories {
err = t.addAllRepositories(sess)
if err != nil {
return fmt.Errorf("addAllRepositories: %v", err)
}
}

// Update organization number of teams.
if _, err = sess.Exec("UPDATE `user` SET num_teams=num_teams+1 WHERE id = ?", t.OrgID); err != nil {
errRollback := sess.Rollback()
Expand Down Expand Up @@ -446,7 +484,7 @@ func GetTeamByID(teamID int64) (*Team, error) {
}

// UpdateTeam updates information of team.
func UpdateTeam(t *Team, authChanged bool) (err error) {
func UpdateTeam(t *Team, authChanged bool, includeAllChanged bool) (err error) {
if len(t.Name) == 0 {
return errors.New("empty team name")
}
Expand Down Expand Up @@ -511,6 +549,14 @@ func UpdateTeam(t *Team, authChanged bool) (err error) {
}
}

// Add all repositories to the team if it has access to all of them.
if includeAllChanged && t.IncludesAllRepositories {
lafriks marked this conversation as resolved.
Show resolved Hide resolved
err = t.addAllRepositories(sess)
if err != nil {
return fmt.Errorf("addAllRepositories: %v", err)
}
}

return sess.Commit()
}

Expand Down
Loading