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 753b7d205b
.
* Clarify help text what options do.
This commit is contained in:
parent
0109229928
commit
72aa5a20ec
17 changed files with 382 additions and 75 deletions
|
@ -57,35 +57,42 @@ func TestAPITeam(t *testing.T) {
|
||||||
teamToCreate := &api.CreateTeamOption{
|
teamToCreate := &api.CreateTeamOption{
|
||||||
Name: "team1",
|
Name: "team1",
|
||||||
Description: "team one",
|
Description: "team one",
|
||||||
|
IncludesAllRepositories: true,
|
||||||
Permission: "write",
|
Permission: "write",
|
||||||
Units: []string{"repo.code", "repo.issues"},
|
Units: []string{"repo.code", "repo.issues"},
|
||||||
}
|
}
|
||||||
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/orgs/%s/teams?token=%s", org.Name, token), teamToCreate)
|
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/orgs/%s/teams?token=%s", org.Name, token), teamToCreate)
|
||||||
resp = session.MakeRequest(t, req, http.StatusCreated)
|
resp = session.MakeRequest(t, req, http.StatusCreated)
|
||||||
DecodeJSON(t, resp, &apiTeam)
|
DecodeJSON(t, resp, &apiTeam)
|
||||||
checkTeamResponse(t, &apiTeam, teamToCreate.Name, teamToCreate.Description, teamToCreate.Permission, teamToCreate.Units)
|
checkTeamResponse(t, &apiTeam, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories,
|
||||||
checkTeamBean(t, apiTeam.ID, teamToCreate.Name, teamToCreate.Description, teamToCreate.Permission, teamToCreate.Units)
|
teamToCreate.Permission, teamToCreate.Units)
|
||||||
|
checkTeamBean(t, apiTeam.ID, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories,
|
||||||
|
teamToCreate.Permission, teamToCreate.Units)
|
||||||
teamID := apiTeam.ID
|
teamID := apiTeam.ID
|
||||||
|
|
||||||
// Edit team.
|
// Edit team.
|
||||||
teamToEdit := &api.EditTeamOption{
|
teamToEdit := &api.EditTeamOption{
|
||||||
Name: "teamone",
|
Name: "teamone",
|
||||||
Description: "team 1",
|
Description: "team 1",
|
||||||
|
IncludesAllRepositories: false,
|
||||||
Permission: "admin",
|
Permission: "admin",
|
||||||
Units: []string{"repo.code", "repo.pulls", "repo.releases"},
|
Units: []string{"repo.code", "repo.pulls", "repo.releases"},
|
||||||
}
|
}
|
||||||
req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d?token=%s", teamID, token), teamToEdit)
|
req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d?token=%s", teamID, token), teamToEdit)
|
||||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||||
DecodeJSON(t, resp, &apiTeam)
|
DecodeJSON(t, resp, &apiTeam)
|
||||||
checkTeamResponse(t, &apiTeam, teamToEdit.Name, teamToEdit.Description, teamToEdit.Permission, teamToEdit.Units)
|
checkTeamResponse(t, &apiTeam, teamToEdit.Name, teamToEdit.Description, teamToEdit.IncludesAllRepositories,
|
||||||
checkTeamBean(t, apiTeam.ID, teamToEdit.Name, teamToEdit.Description, teamToEdit.Permission, teamToEdit.Units)
|
teamToEdit.Permission, teamToEdit.Units)
|
||||||
|
checkTeamBean(t, apiTeam.ID, teamToEdit.Name, teamToEdit.Description, teamToEdit.IncludesAllRepositories,
|
||||||
|
teamToEdit.Permission, teamToEdit.Units)
|
||||||
|
|
||||||
// Read team.
|
// Read team.
|
||||||
teamRead := models.AssertExistsAndLoadBean(t, &models.Team{ID: teamID}).(*models.Team)
|
teamRead := models.AssertExistsAndLoadBean(t, &models.Team{ID: teamID}).(*models.Team)
|
||||||
req = NewRequestf(t, "GET", "/api/v1/teams/%d?token="+token, teamID)
|
req = NewRequestf(t, "GET", "/api/v1/teams/%d?token="+token, teamID)
|
||||||
resp = session.MakeRequest(t, req, http.StatusOK)
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||||
DecodeJSON(t, resp, &apiTeam)
|
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.
|
// Delete team.
|
||||||
req = NewRequestf(t, "DELETE", "/api/v1/teams/%d?token="+token, teamID)
|
req = NewRequestf(t, "DELETE", "/api/v1/teams/%d?token="+token, teamID)
|
||||||
|
@ -93,19 +100,20 @@ func TestAPITeam(t *testing.T) {
|
||||||
models.AssertNotExistsBean(t, &models.Team{ID: teamID})
|
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, name, apiTeam.Name, "name")
|
||||||
assert.Equal(t, description, apiTeam.Description, "description")
|
assert.Equal(t, description, apiTeam.Description, "description")
|
||||||
|
assert.Equal(t, includesAllRepositories, apiTeam.IncludesAllRepositories, "includesAllRepositories")
|
||||||
assert.Equal(t, permission, apiTeam.Permission, "permission")
|
assert.Equal(t, permission, apiTeam.Permission, "permission")
|
||||||
sort.StringSlice(units).Sort()
|
sort.StringSlice(units).Sort()
|
||||||
sort.StringSlice(apiTeam.Units).Sort()
|
sort.StringSlice(apiTeam.Units).Sort()
|
||||||
assert.EqualValues(t, units, apiTeam.Units, "units")
|
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)
|
team := models.AssertExistsAndLoadBean(t, &models.Team{ID: id}).(*models.Team)
|
||||||
assert.NoError(t, team.GetUnits(), "GetUnits")
|
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 {
|
type TeamSearchResults struct {
|
||||||
|
|
|
@ -264,6 +264,8 @@ var migrations = []Migration{
|
||||||
NewMigration("Add WhitelistDeployKeys to protected branch", addWhitelistDeployKeysToBranches),
|
NewMigration("Add WhitelistDeployKeys to protected branch", addWhitelistDeployKeysToBranches),
|
||||||
// v104 -> v105
|
// v104 -> v105
|
||||||
NewMigration("remove unnecessary columns from label", removeLabelUneededCols),
|
NewMigration("remove unnecessary columns from label", removeLabelUneededCols),
|
||||||
|
// v105 -> v106
|
||||||
|
NewMigration("add includes_all_repositories to teams", addTeamIncludesAllRepositories),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Migrate database to current version
|
// Migrate database to current version
|
||||||
|
|
25
models/migrations/v105.go
Normal file
25
models/migrations/v105.go
Normal file
|
@ -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
|
||||||
|
}
|
|
@ -48,6 +48,9 @@ func (org *User) GetOwnerTeam() (*Team, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (org *User) getTeams(e Engine) error {
|
func (org *User) getTeams(e Engine) error {
|
||||||
|
if org.Teams != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return e.
|
return e.
|
||||||
Where("org_id=?", org.ID).
|
Where("org_id=?", org.ID).
|
||||||
OrderBy("CASE WHEN name LIKE '" + ownerTeamName + "' THEN '' ELSE name END").
|
OrderBy("CASE WHEN name LIKE '" + ownerTeamName + "' THEN '' ELSE name END").
|
||||||
|
@ -154,6 +157,7 @@ func CreateOrganization(org, owner *User) (err error) {
|
||||||
Name: ownerTeamName,
|
Name: ownerTeamName,
|
||||||
Authorize: AccessModeOwner,
|
Authorize: AccessModeOwner,
|
||||||
NumMembers: 1,
|
NumMembers: 1,
|
||||||
|
IncludesAllRepositories: true,
|
||||||
}
|
}
|
||||||
if _, err = sess.Insert(t); err != nil {
|
if _, err = sess.Insert(t); err != nil {
|
||||||
return fmt.Errorf("insert owner team: %v", err)
|
return fmt.Errorf("insert owner team: %v", err)
|
||||||
|
|
|
@ -33,6 +33,7 @@ type Team struct {
|
||||||
NumRepos int
|
NumRepos int
|
||||||
NumMembers int
|
NumMembers int
|
||||||
Units []*TeamUnit `xorm:"-"`
|
Units []*TeamUnit `xorm:"-"`
|
||||||
|
IncludesAllRepositories bool `xorm:"NOT NULL DEFAULT false"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SearchTeamOptions holds the search options
|
// SearchTeamOptions holds the search options
|
||||||
|
@ -149,6 +150,9 @@ func (t *Team) IsMember(userID int64) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Team) getRepositories(e Engine) error {
|
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").
|
return e.Join("INNER", "team_repo", "repository.id = team_repo.repo_id").
|
||||||
Where("team_repo.team_id=?", t.ID).
|
Where("team_repo.team_id=?", t.ID).
|
||||||
OrderBy("repository.name").
|
OrderBy("repository.name").
|
||||||
|
@ -220,6 +224,25 @@ func (t *Team) addRepository(e Engine, repo *Repository) (err error) {
|
||||||
return nil
|
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 {
|
||||||
|
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.
|
// AddRepository adds new repository to team of organization.
|
||||||
func (t *Team) AddRepository(repo *Repository) (err error) {
|
func (t *Team) AddRepository(repo *Repository) (err error) {
|
||||||
if repo.OwnerID != t.OrgID {
|
if repo.OwnerID != t.OrgID {
|
||||||
|
@ -241,6 +264,8 @@ func (t *Team) AddRepository(repo *Repository) (err error) {
|
||||||
return sess.Commit()
|
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) {
|
func (t *Team) removeRepository(e Engine, repo *Repository, recalculate bool) (err error) {
|
||||||
if err = removeTeamRepo(e, t.ID, repo.ID); err != nil {
|
if err = removeTeamRepo(e, t.ID, repo.ID); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -284,11 +309,16 @@ func (t *Team) removeRepository(e Engine, repo *Repository, recalculate bool) (e
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveRepository removes repository from team of organization.
|
// 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 {
|
func (t *Team) RemoveRepository(repoID int64) error {
|
||||||
if !t.HasRepository(repoID) {
|
if !t.HasRepository(repoID) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if t.IncludesAllRepositories {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
repo, err := GetRepositoryByID(repoID)
|
repo, err := GetRepositoryByID(repoID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -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.
|
// Update organization number of teams.
|
||||||
if _, err = sess.Exec("UPDATE `user` SET num_teams=num_teams+1 WHERE id = ?", t.OrgID); err != nil {
|
if _, err = sess.Exec("UPDATE `user` SET num_teams=num_teams+1 WHERE id = ?", t.OrgID); err != nil {
|
||||||
errRollback := sess.Rollback()
|
errRollback := sess.Rollback()
|
||||||
|
@ -446,7 +484,7 @@ func GetTeamByID(teamID int64) (*Team, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateTeam updates information of team.
|
// 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 {
|
if len(t.Name) == 0 {
|
||||||
return errors.New("empty team name")
|
return errors.New("empty team name")
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
err = t.addAllRepositories(sess)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("addAllRepositories: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return sess.Commit()
|
return sess.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,9 +5,12 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/structs"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -206,7 +209,7 @@ func TestUpdateTeam(t *testing.T) {
|
||||||
team.Name = "newName"
|
team.Name = "newName"
|
||||||
team.Description = strings.Repeat("A long description!", 100)
|
team.Description = strings.Repeat("A long description!", 100)
|
||||||
team.Authorize = AccessModeAdmin
|
team.Authorize = AccessModeAdmin
|
||||||
assert.NoError(t, UpdateTeam(team, true))
|
assert.NoError(t, UpdateTeam(team, true, false))
|
||||||
|
|
||||||
team = AssertExistsAndLoadBean(t, &Team{Name: "newName"}).(*Team)
|
team = AssertExistsAndLoadBean(t, &Team{Name: "newName"}).(*Team)
|
||||||
assert.True(t, strings.HasPrefix(team.Description, "A long description!"))
|
assert.True(t, strings.HasPrefix(team.Description, "A long description!"))
|
||||||
|
@ -225,7 +228,7 @@ func TestUpdateTeam2(t *testing.T) {
|
||||||
team.LowerName = "owners"
|
team.LowerName = "owners"
|
||||||
team.Name = "Owners"
|
team.Name = "Owners"
|
||||||
team.Description = strings.Repeat("A long description!", 100)
|
team.Description = strings.Repeat("A long description!", 100)
|
||||||
err := UpdateTeam(team, true)
|
err := UpdateTeam(team, true, false)
|
||||||
assert.True(t, IsErrTeamAlreadyExist(err))
|
assert.True(t, IsErrTeamAlreadyExist(err))
|
||||||
|
|
||||||
CheckConsistencyFor(t, &Team{ID: team.ID})
|
CheckConsistencyFor(t, &Team{ID: team.ID})
|
||||||
|
@ -374,3 +377,133 @@ func TestUsersInTeamsCount(t *testing.T) {
|
||||||
test([]int64{1, 2, 3, 4, 5}, []int64{2, 5}, 2) // userid 2,4
|
test([]int64{1, 2, 3, 4, 5}, []int64{2, 5}, 2) // userid 2,4
|
||||||
test([]int64{1, 2, 3, 4, 5}, []int64{2, 3, 5}, 3) // userid 2,4,5
|
test([]int64{1, 2, 3, 4, 5}, []int64{2, 3, 5}, 3) // userid 2,4,5
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIncludesAllRepositoriesTeams(t *testing.T) {
|
||||||
|
assert.NoError(t, PrepareTestDatabase())
|
||||||
|
|
||||||
|
testTeamRepositories := func(teamID int64, repoIds []int64) {
|
||||||
|
team := AssertExistsAndLoadBean(t, &Team{ID: teamID}).(*Team)
|
||||||
|
assert.NoError(t, team.GetRepositories(), "%s: GetRepositories", team.Name)
|
||||||
|
assert.Len(t, team.Repos, team.NumRepos, "%s: len repo", team.Name)
|
||||||
|
assert.Equal(t, len(repoIds), len(team.Repos), "%s: repo count", team.Name)
|
||||||
|
for i, rid := range repoIds {
|
||||||
|
if rid > 0 {
|
||||||
|
assert.True(t, team.HasRepository(rid), "%s: HasRepository(%d) %d", rid, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get an admin user.
|
||||||
|
user, err := GetUserByID(1)
|
||||||
|
assert.NoError(t, err, "GetUserByID")
|
||||||
|
|
||||||
|
// Create org.
|
||||||
|
org := &User{
|
||||||
|
Name: "All repo",
|
||||||
|
IsActive: true,
|
||||||
|
Type: UserTypeOrganization,
|
||||||
|
Visibility: structs.VisibleTypePublic,
|
||||||
|
}
|
||||||
|
assert.NoError(t, CreateOrganization(org, user), "CreateOrganization")
|
||||||
|
|
||||||
|
// Check Owner team.
|
||||||
|
ownerTeam, err := org.GetOwnerTeam()
|
||||||
|
assert.NoError(t, err, "GetOwnerTeam")
|
||||||
|
assert.True(t, ownerTeam.IncludesAllRepositories, "Owner team includes all repositories")
|
||||||
|
|
||||||
|
// Create repos.
|
||||||
|
repoIds := make([]int64, 0)
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
r, err := CreateRepository(user, org, CreateRepoOptions{Name: fmt.Sprintf("repo-%d", i)})
|
||||||
|
assert.NoError(t, err, "CreateRepository %d", i)
|
||||||
|
if r != nil {
|
||||||
|
repoIds = append(repoIds, r.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Get fresh copy of Owner team after creating repos.
|
||||||
|
ownerTeam, err = org.GetOwnerTeam()
|
||||||
|
assert.NoError(t, err, "GetOwnerTeam")
|
||||||
|
|
||||||
|
// Create teams and check repositories.
|
||||||
|
teams := []*Team{
|
||||||
|
ownerTeam,
|
||||||
|
{
|
||||||
|
OrgID: org.ID,
|
||||||
|
Name: "team one",
|
||||||
|
Authorize: AccessModeRead,
|
||||||
|
IncludesAllRepositories: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
OrgID: org.ID,
|
||||||
|
Name: "team 2",
|
||||||
|
Authorize: AccessModeRead,
|
||||||
|
IncludesAllRepositories: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
OrgID: org.ID,
|
||||||
|
Name: "team three",
|
||||||
|
Authorize: AccessModeWrite,
|
||||||
|
IncludesAllRepositories: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
OrgID: org.ID,
|
||||||
|
Name: "team 4",
|
||||||
|
Authorize: AccessModeWrite,
|
||||||
|
IncludesAllRepositories: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
teamRepos := [][]int64{
|
||||||
|
repoIds,
|
||||||
|
repoIds,
|
||||||
|
{},
|
||||||
|
repoIds,
|
||||||
|
{},
|
||||||
|
}
|
||||||
|
for i, team := range teams {
|
||||||
|
if i > 0 { // first team is Owner.
|
||||||
|
assert.NoError(t, NewTeam(team), "%s: NewTeam", team.Name)
|
||||||
|
}
|
||||||
|
testTeamRepositories(team.ID, teamRepos[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update teams and check repositories.
|
||||||
|
teams[3].IncludesAllRepositories = false
|
||||||
|
teams[4].IncludesAllRepositories = true
|
||||||
|
teamRepos[4] = repoIds
|
||||||
|
for i, team := range teams {
|
||||||
|
assert.NoError(t, UpdateTeam(team, false, true), "%s: UpdateTeam", team.Name)
|
||||||
|
testTeamRepositories(team.ID, teamRepos[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create repo and check teams repositories.
|
||||||
|
org.Teams = nil // Reset teams to allow their reloading.
|
||||||
|
r, err := CreateRepository(user, org, CreateRepoOptions{Name: "repo-last"})
|
||||||
|
assert.NoError(t, err, "CreateRepository last")
|
||||||
|
if r != nil {
|
||||||
|
repoIds = append(repoIds, r.ID)
|
||||||
|
}
|
||||||
|
teamRepos[0] = repoIds
|
||||||
|
teamRepos[1] = repoIds
|
||||||
|
teamRepos[4] = repoIds
|
||||||
|
for i, team := range teams {
|
||||||
|
testTeamRepositories(team.ID, teamRepos[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove repo and check teams repositories.
|
||||||
|
assert.NoError(t, DeleteRepository(user, org.ID, repoIds[0]), "DeleteRepository")
|
||||||
|
teamRepos[0] = repoIds[1:]
|
||||||
|
teamRepos[1] = repoIds[1:]
|
||||||
|
teamRepos[3] = repoIds[1:3]
|
||||||
|
teamRepos[4] = repoIds[1:]
|
||||||
|
for i, team := range teams {
|
||||||
|
testTeamRepositories(team.ID, teamRepos[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wipe created items.
|
||||||
|
for i, rid := range repoIds {
|
||||||
|
if i > 0 { // first repo already deleted.
|
||||||
|
assert.NoError(t, DeleteRepository(user, org.ID, rid), "DeleteRepository %d", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert.NoError(t, DeleteOrganization(org), "DeleteOrganization")
|
||||||
|
}
|
||||||
|
|
|
@ -1447,15 +1447,18 @@ func createRepository(e *xorm.Session, doer, u *User, repo *Repository) (err err
|
||||||
}
|
}
|
||||||
u.NumRepos++
|
u.NumRepos++
|
||||||
|
|
||||||
// Give access to all members in owner team.
|
// Give access to all members in teams with access to all repositories.
|
||||||
if u.IsOrganization() {
|
if u.IsOrganization() {
|
||||||
t, err := u.getOwnerTeam(e)
|
if err := u.GetTeams(); err != nil {
|
||||||
if err != nil {
|
return fmt.Errorf("GetTeams: %v", err)
|
||||||
return fmt.Errorf("getOwnerTeam: %v", err)
|
|
||||||
}
|
}
|
||||||
if err = t.addRepository(e, repo); err != nil {
|
for _, t := range u.Teams {
|
||||||
|
if t.IncludesAllRepositories {
|
||||||
|
if err := t.addRepository(e, repo); err != nil {
|
||||||
return fmt.Errorf("addRepository: %v", err)
|
return fmt.Errorf("addRepository: %v", err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if err = repo.recalculateAccesses(e); err != nil {
|
} else if err = repo.recalculateAccesses(e); err != nil {
|
||||||
// Organization automatically called this in addRepository method.
|
// Organization automatically called this in addRepository method.
|
||||||
return fmt.Errorf("recalculateAccesses: %v", err)
|
return fmt.Errorf("recalculateAccesses: %v", err)
|
||||||
|
@ -1641,11 +1644,15 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) error
|
||||||
}
|
}
|
||||||
|
|
||||||
if newOwner.IsOrganization() {
|
if newOwner.IsOrganization() {
|
||||||
t, err := newOwner.getOwnerTeam(sess)
|
if err := newOwner.GetTeams(); err != nil {
|
||||||
if err != nil {
|
return fmt.Errorf("GetTeams: %v", err)
|
||||||
return fmt.Errorf("getOwnerTeam: %v", err)
|
}
|
||||||
} else if err = t.addRepository(sess, repo); err != nil {
|
for _, t := range newOwner.Teams {
|
||||||
return fmt.Errorf("add to owner team: %v", err)
|
if t.IncludesAllRepositories {
|
||||||
|
if err := t.addRepository(sess, repo); err != nil {
|
||||||
|
return fmt.Errorf("addRepository: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if err = repo.recalculateAccesses(sess); err != nil {
|
} else if err = repo.recalculateAccesses(sess); err != nil {
|
||||||
// Organization called this in addRepository method.
|
// Organization called this in addRepository method.
|
||||||
|
|
|
@ -62,6 +62,7 @@ type CreateTeamForm struct {
|
||||||
Description string `binding:"MaxSize(255)"`
|
Description string `binding:"MaxSize(255)"`
|
||||||
Permission string
|
Permission string
|
||||||
Units []models.UnitType
|
Units []models.UnitType
|
||||||
|
RepoAccess string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate validates the fields
|
// Validate validates the fields
|
||||||
|
|
|
@ -11,6 +11,7 @@ type Team struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Organization *Organization `json:"organization"`
|
Organization *Organization `json:"organization"`
|
||||||
|
IncludesAllRepositories bool `json:"includes_all_repositories"`
|
||||||
// enum: none,read,write,admin,owner
|
// enum: none,read,write,admin,owner
|
||||||
Permission string `json:"permission"`
|
Permission string `json:"permission"`
|
||||||
// example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.ext_wiki"]
|
// example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.ext_wiki"]
|
||||||
|
@ -22,6 +23,7 @@ type CreateTeamOption struct {
|
||||||
// required: true
|
// required: true
|
||||||
Name string `json:"name" binding:"Required;AlphaDashDot;MaxSize(30)"`
|
Name string `json:"name" binding:"Required;AlphaDashDot;MaxSize(30)"`
|
||||||
Description string `json:"description" binding:"MaxSize(255)"`
|
Description string `json:"description" binding:"MaxSize(255)"`
|
||||||
|
IncludesAllRepositories bool `json:"includes_all_repositories"`
|
||||||
// enum: read,write,admin
|
// enum: read,write,admin
|
||||||
Permission string `json:"permission"`
|
Permission string `json:"permission"`
|
||||||
// example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.ext_wiki"]
|
// example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.ext_wiki"]
|
||||||
|
@ -33,6 +35,7 @@ type EditTeamOption struct {
|
||||||
// required: true
|
// required: true
|
||||||
Name string `json:"name" binding:"Required;AlphaDashDot;MaxSize(30)"`
|
Name string `json:"name" binding:"Required;AlphaDashDot;MaxSize(30)"`
|
||||||
Description string `json:"description" binding:"MaxSize(255)"`
|
Description string `json:"description" binding:"MaxSize(255)"`
|
||||||
|
IncludesAllRepositories bool `json:"includes_all_repositories"`
|
||||||
// enum: read,write,admin
|
// enum: read,write,admin
|
||||||
Permission string `json:"permission"`
|
Permission string `json:"permission"`
|
||||||
// example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.ext_wiki"]
|
// example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.ext_wiki"]
|
||||||
|
|
|
@ -1515,6 +1515,7 @@ team_name = Team Name
|
||||||
team_desc = Description
|
team_desc = Description
|
||||||
team_name_helper = Team names should be short and memorable.
|
team_name_helper = Team names should be short and memorable.
|
||||||
team_desc_helper = Describe the purpose or role of the team.
|
team_desc_helper = Describe the purpose or role of the team.
|
||||||
|
team_access_desc = Repository access
|
||||||
team_permission_desc = Permission
|
team_permission_desc = Permission
|
||||||
team_unit_desc = Allow Access to Repository Sections
|
team_unit_desc = Allow Access to Repository Sections
|
||||||
|
|
||||||
|
@ -1588,6 +1589,13 @@ teams.add_nonexistent_repo = "The repository you're trying to add does not exist
|
||||||
teams.add_duplicate_users = User is already a team member.
|
teams.add_duplicate_users = User is already a team member.
|
||||||
teams.repos.none = No repositories could be accessed by this team.
|
teams.repos.none = No repositories could be accessed by this team.
|
||||||
teams.members.none = No members on this team.
|
teams.members.none = No members on this team.
|
||||||
|
teams.specific_repositories = Specific repositories
|
||||||
|
teams.specific_repositories_helper = Members will only have access to repositories explicitly added to the team. Selecting this <strong>will not</strong> automatically remove repositories already added with <i>All repositories</i>.
|
||||||
|
teams.all_repositories = All repositories
|
||||||
|
teams.all_repositories_helper = Team has access to all repositories. Selecting this will <strong>add all existing</strong> repositories to the team.
|
||||||
|
teams.all_repositories_read_permission_desc = This team grants <strong>Read</strong> access to <strong>all repositories</strong>: members can view and clone repositories.
|
||||||
|
teams.all_repositories_write_permission_desc = This team grants <strong>Write</strong> access to <strong>all repositories</strong>: members can read from and push to repositories.
|
||||||
|
teams.all_repositories_admin_permission_desc = This team grants <strong>Admin</strong> access to <strong>all repositories</strong>: members can read from, push to and add collaborators to repositories.
|
||||||
|
|
||||||
[admin]
|
[admin]
|
||||||
dashboard = Dashboard
|
dashboard = Dashboard
|
||||||
|
|
|
@ -230,6 +230,7 @@ func ToTeam(team *models.Team) *api.Team {
|
||||||
ID: team.ID,
|
ID: team.ID,
|
||||||
Name: team.Name,
|
Name: team.Name,
|
||||||
Description: team.Description,
|
Description: team.Description,
|
||||||
|
IncludesAllRepositories: team.IncludesAllRepositories,
|
||||||
Permission: team.Authorize.String(),
|
Permission: team.Authorize.String(),
|
||||||
Units: team.GetUnitNames(),
|
Units: team.GetUnitNames(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -131,6 +131,7 @@ func CreateTeam(ctx *context.APIContext, form api.CreateTeamOption) {
|
||||||
OrgID: ctx.Org.Organization.ID,
|
OrgID: ctx.Org.Organization.ID,
|
||||||
Name: form.Name,
|
Name: form.Name,
|
||||||
Description: form.Description,
|
Description: form.Description,
|
||||||
|
IncludesAllRepositories: form.IncludesAllRepositories,
|
||||||
Authorize: models.ParseAccessMode(form.Permission),
|
Authorize: models.ParseAccessMode(form.Permission),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,11 +183,27 @@ func EditTeam(ctx *context.APIContext, form api.EditTeamOption) {
|
||||||
// "200":
|
// "200":
|
||||||
// "$ref": "#/responses/Team"
|
// "$ref": "#/responses/Team"
|
||||||
team := ctx.Org.Team
|
team := ctx.Org.Team
|
||||||
team.Name = form.Name
|
|
||||||
team.Description = form.Description
|
team.Description = form.Description
|
||||||
team.Authorize = models.ParseAccessMode(form.Permission)
|
|
||||||
unitTypes := models.FindUnitTypes(form.Units...)
|
unitTypes := models.FindUnitTypes(form.Units...)
|
||||||
|
|
||||||
|
isAuthChanged := false
|
||||||
|
isIncludeAllChanged := false
|
||||||
|
if !team.IsOwnerTeam() {
|
||||||
|
// Validate permission level.
|
||||||
|
auth := models.ParseAccessMode(form.Permission)
|
||||||
|
|
||||||
|
team.Name = form.Name
|
||||||
|
if team.Authorize != auth {
|
||||||
|
isAuthChanged = true
|
||||||
|
team.Authorize = auth
|
||||||
|
}
|
||||||
|
|
||||||
|
if team.IncludesAllRepositories != form.IncludesAllRepositories {
|
||||||
|
isIncludeAllChanged = true
|
||||||
|
team.IncludesAllRepositories = form.IncludesAllRepositories
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if team.Authorize < models.AccessModeOwner {
|
if team.Authorize < models.AccessModeOwner {
|
||||||
var units = make([]*models.TeamUnit, 0, len(form.Units))
|
var units = make([]*models.TeamUnit, 0, len(form.Units))
|
||||||
for _, tp := range unitTypes {
|
for _, tp := range unitTypes {
|
||||||
|
@ -198,7 +215,7 @@ func EditTeam(ctx *context.APIContext, form api.EditTeamOption) {
|
||||||
team.Units = units
|
team.Units = units
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := models.UpdateTeam(team, true); err != nil {
|
if err := models.UpdateTeam(team, isAuthChanged, isIncludeAllChanged); err != nil {
|
||||||
ctx.Error(500, "EditTeam", err)
|
ctx.Error(500, "EditTeam", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||||
|
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a MIT-style
|
// Use of this source code is governed by a MIT-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
@ -180,12 +181,14 @@ func NewTeamPost(ctx *context.Context, form auth.CreateTeamForm) {
|
||||||
ctx.Data["PageIsOrgTeams"] = true
|
ctx.Data["PageIsOrgTeams"] = true
|
||||||
ctx.Data["PageIsOrgTeamsNew"] = true
|
ctx.Data["PageIsOrgTeamsNew"] = true
|
||||||
ctx.Data["Units"] = models.Units
|
ctx.Data["Units"] = models.Units
|
||||||
|
var includesAllRepositories = (form.RepoAccess == "all")
|
||||||
|
|
||||||
t := &models.Team{
|
t := &models.Team{
|
||||||
OrgID: ctx.Org.Organization.ID,
|
OrgID: ctx.Org.Organization.ID,
|
||||||
Name: form.TeamName,
|
Name: form.TeamName,
|
||||||
Description: form.Description,
|
Description: form.Description,
|
||||||
Authorize: models.ParseAccessMode(form.Permission),
|
Authorize: models.ParseAccessMode(form.Permission),
|
||||||
|
IncludesAllRepositories: includesAllRepositories,
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.Authorize < models.AccessModeOwner {
|
if t.Authorize < models.AccessModeOwner {
|
||||||
|
@ -268,6 +271,8 @@ func EditTeamPost(ctx *context.Context, form auth.CreateTeamForm) {
|
||||||
ctx.Data["Units"] = models.Units
|
ctx.Data["Units"] = models.Units
|
||||||
|
|
||||||
isAuthChanged := false
|
isAuthChanged := false
|
||||||
|
isIncludeAllChanged := false
|
||||||
|
var includesAllRepositories = (form.RepoAccess == "all")
|
||||||
if !t.IsOwnerTeam() {
|
if !t.IsOwnerTeam() {
|
||||||
// Validate permission level.
|
// Validate permission level.
|
||||||
auth := models.ParseAccessMode(form.Permission)
|
auth := models.ParseAccessMode(form.Permission)
|
||||||
|
@ -277,6 +282,11 @@ func EditTeamPost(ctx *context.Context, form auth.CreateTeamForm) {
|
||||||
isAuthChanged = true
|
isAuthChanged = true
|
||||||
t.Authorize = auth
|
t.Authorize = auth
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if t.IncludesAllRepositories != includesAllRepositories {
|
||||||
|
isIncludeAllChanged = true
|
||||||
|
t.IncludesAllRepositories = includesAllRepositories
|
||||||
|
}
|
||||||
}
|
}
|
||||||
t.Description = form.Description
|
t.Description = form.Description
|
||||||
if t.Authorize < models.AccessModeOwner {
|
if t.Authorize < models.AccessModeOwner {
|
||||||
|
@ -305,7 +315,7 @@ func EditTeamPost(ctx *context.Context, form auth.CreateTeamForm) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := models.UpdateTeam(t, isAuthChanged); err != nil {
|
if err := models.UpdateTeam(t, isAuthChanged, isIncludeAllChanged); err != nil {
|
||||||
ctx.Data["Err_TeamName"] = true
|
ctx.Data["Err_TeamName"] = true
|
||||||
switch {
|
switch {
|
||||||
case models.IsErrTeamAlreadyExist(err):
|
case models.IsErrTeamAlreadyExist(err):
|
||||||
|
|
|
@ -24,6 +24,24 @@
|
||||||
<span class="help">{{.i18n.Tr "org.team_desc_helper"}}</span>
|
<span class="help">{{.i18n.Tr "org.team_desc_helper"}}</span>
|
||||||
</div>
|
</div>
|
||||||
{{if not (eq .Team.LowerName "owners")}}
|
{{if not (eq .Team.LowerName "owners")}}
|
||||||
|
<div class="grouped field">
|
||||||
|
<label>{{.i18n.Tr "org.team_access_desc"}}</label>
|
||||||
|
<br>
|
||||||
|
<div class="field">
|
||||||
|
<div class="ui radio checkbox">
|
||||||
|
<input type="radio" name="repo_access" value="specific" {{if not .Team.IncludesAllRepositories}}checked{{end}}>
|
||||||
|
<label>{{.i18n.Tr "org.teams.specific_repositories"}}</label>
|
||||||
|
<span class="help">{{.i18n.Tr "org.teams.specific_repositories_helper"}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<div class="ui radio checkbox">
|
||||||
|
<input type="radio" name="repo_access" value="all" {{if .Team.IncludesAllRepositories}}checked{{end}}>
|
||||||
|
<label>{{.i18n.Tr "org.teams.all_repositories"}}</label>
|
||||||
|
<span class="help">{{.i18n.Tr "org.teams.all_repositories_helper"}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="grouped field">
|
<div class="grouped field">
|
||||||
<label>{{.i18n.Tr "org.team_permission_desc"}}</label>
|
<label>{{.i18n.Tr "org.team_permission_desc"}}</label>
|
||||||
<br>
|
<br>
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
{{template "org/team/sidebar" .}}
|
{{template "org/team/sidebar" .}}
|
||||||
<div class="ui ten wide column">
|
<div class="ui ten wide column">
|
||||||
{{template "org/team/navbar" .}}
|
{{template "org/team/navbar" .}}
|
||||||
{{$canAddRemove := and $.IsOrganizationOwner (not (eq $.Team.LowerName "owners"))}}
|
{{$canAddRemove := and $.IsOrganizationOwner (not $.Team.IncludesAllRepositories)}}
|
||||||
{{if $canAddRemove}}
|
{{if $canAddRemove}}
|
||||||
<div class="ui attached segment">
|
<div class="ui attached segment">
|
||||||
<form class="ui form" id="add-repo-form" action="{{$.OrgLink}}/teams/{{$.Team.LowerName}}/action/repo/add" method="post">
|
<form class="ui form" id="add-repo-form" action="{{$.OrgLink}}/teams/{{$.Team.LowerName}}/action/repo/add" method="post">
|
||||||
|
|
|
@ -22,12 +22,24 @@
|
||||||
{{if eq .Team.LowerName "owners"}}
|
{{if eq .Team.LowerName "owners"}}
|
||||||
{{.i18n.Tr "org.teams.owners_permission_desc" | Str2html}}
|
{{.i18n.Tr "org.teams.owners_permission_desc" | Str2html}}
|
||||||
{{else if (eq .Team.Authorize 1)}}
|
{{else if (eq .Team.Authorize 1)}}
|
||||||
|
{{if .Team.IncludesAllRepositories}}
|
||||||
|
{{.i18n.Tr "org.teams.all_repositories_read_permission_desc" | Str2html}}
|
||||||
|
{{else}}
|
||||||
{{.i18n.Tr "org.teams.read_permission_desc" | Str2html}}
|
{{.i18n.Tr "org.teams.read_permission_desc" | Str2html}}
|
||||||
|
{{end}}
|
||||||
{{else if (eq .Team.Authorize 2)}}
|
{{else if (eq .Team.Authorize 2)}}
|
||||||
|
{{if .Team.IncludesAllRepositories}}
|
||||||
|
{{.i18n.Tr "org.teams.all_repositories_write_permission_desc" | Str2html}}
|
||||||
|
{{else}}
|
||||||
{{.i18n.Tr "org.teams.write_permission_desc" | Str2html}}
|
{{.i18n.Tr "org.teams.write_permission_desc" | Str2html}}
|
||||||
|
{{end}}
|
||||||
{{else if (eq .Team.Authorize 3)}}
|
{{else if (eq .Team.Authorize 3)}}
|
||||||
|
{{if .Team.IncludesAllRepositories}}
|
||||||
|
{{.i18n.Tr "org.teams.all_repositories_admin_permission_desc" | Str2html}}
|
||||||
|
{{else}}
|
||||||
{{.i18n.Tr "org.teams.admin_permission_desc" | Str2html}}
|
{{.i18n.Tr "org.teams.admin_permission_desc" | Str2html}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{if .IsOrganizationOwner}}
|
{{if .IsOrganizationOwner}}
|
||||||
|
|
|
@ -8242,6 +8242,10 @@
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"x-go-name": "Description"
|
"x-go-name": "Description"
|
||||||
},
|
},
|
||||||
|
"includes_all_repositories": {
|
||||||
|
"type": "boolean",
|
||||||
|
"x-go-name": "IncludesAllRepositories"
|
||||||
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"x-go-name": "Name"
|
"x-go-name": "Name"
|
||||||
|
@ -8801,6 +8805,10 @@
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"x-go-name": "Description"
|
"x-go-name": "Description"
|
||||||
},
|
},
|
||||||
|
"includes_all_repositories": {
|
||||||
|
"type": "boolean",
|
||||||
|
"x-go-name": "IncludesAllRepositories"
|
||||||
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"x-go-name": "Name"
|
"x-go-name": "Name"
|
||||||
|
@ -10457,6 +10465,10 @@
|
||||||
"format": "int64",
|
"format": "int64",
|
||||||
"x-go-name": "ID"
|
"x-go-name": "ID"
|
||||||
},
|
},
|
||||||
|
"includes_all_repositories": {
|
||||||
|
"type": "boolean",
|
||||||
|
"x-go-name": "IncludesAllRepositories"
|
||||||
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"x-go-name": "Name"
|
"x-go-name": "Name"
|
||||||
|
|
Loading…
Reference in a new issue