diff --git a/docs/content/doc/usage/permissions.en-us.md b/docs/content/doc/usage/permissions.en-us.md
new file mode 100644
index 0000000000000..1eea78b557134
--- /dev/null
+++ b/docs/content/doc/usage/permissions.en-us.md
@@ -0,0 +1,73 @@
+---
+date: "2021-12-13:10:10+08:00"
+title: "Permissions"
+slug: "permissions"
+weight: 14
+toc: false
+draft: false
+menu:
+ sidebar:
+ parent: "usage"
+ name: "Permissions"
+ weight: 14
+ identifier: "permissions"
+---
+
+# Permissions
+
+**Table of Contents**
+
+{{< toc >}}
+
+Gitea supports permissions for repository so that you can give different access for different people. At first, we need to know about `Unit`.
+
+## Unit
+
+In Gitea, we call a sub module of a repository `Unit`. Now we have following units.
+
+| Name | Description | Permissions |
+| --------------- | ---------------------------------------------------- | ----------- |
+| Code | Access source code, files, commits and branches. | Read Write |
+| Issues | Organize bug reports, tasks and milestones. | Read Write |
+| PullRequests | Enable pull requests and code reviews. | Read Write |
+| Releases | Track project versions and downloads. | Read Write |
+| Wiki | Write and share documentation with collaborators. | Read Write |
+| ExternalWiki | Link to an external wiki | Read |
+| ExternalTracker | Link to an external issue tracker | Read |
+| Projects | The URL to the template repository | Read Write |
+| Settings | Manage the repository | Admin |
+
+With different permissions, people could do different things with these units.
+
+| Name | Read | Write | Admin |
+| --------------- | ------------------------------------------------- | ---------------------------- | ------------------------- |
+| Code | View code trees, files, commits, branches and etc. | Push codes. | - |
+| Issues | View issues and create new issues. | Add labels, assign, close | - |
+| PullRequests | View pull requests and create new pull requests. | Add labels, assign, close | - |
+| Releases | View releases and download files. | Create/Edit releases | - |
+| Wiki | View wiki pages. Clone the wiki repository. | Create/Edit wiki pages, push | - |
+| ExternalWiki | Link to an external wiki | - | - |
+| ExternalTracker | Link to an external issue tracker | - | - |
+| Projects | View the boards | Change issues across boards | - |
+| Settings | - | - | Manage the repository |
+
+And there are some differences for permissions between individual repositories and organization repositories.
+
+## Individual Repository
+
+For individual repositories, the creators are the only owners of repositories and have no limit to change anything of this
+repository or delete it. Repositories owners could add collaborators to help maintain the repositories. Collaborators could have `Read`, `Write` and `Admin` permissions.
+
+## Organization Repository
+
+Different from individual repositories, the owner of organization repositories are the owner team of this organization.
+
+### Team
+
+A team in an organization has unit permissions settings. It can have members and repositories scope. A team could access all the repositories in this organization or special repositories changed by the owner team. A team could also be allowed to create new
+repositories.
+
+The owner team will be created when the organization created and the creator will become the first member of the owner team.
+Notice Gitea will not allow a people is a member of organization but not in any team. The owner team could not be deleted and only
+members of owner team could create a new team. Admin team could be created to manage some of repositories, members of admin team
+could do anything with these repositories. Generate team could be created by the owner team to do the permissions allowed operations.
diff --git a/integrations/api_repo_teams_test.go b/integrations/api_repo_teams_test.go
index 07a8b9418e1eb..a3baeba63c804 100644
--- a/integrations/api_repo_teams_test.go
+++ b/integrations/api_repo_teams_test.go
@@ -10,9 +10,11 @@ import (
"testing"
repo_model "code.gitea.io/gitea/models/repo"
+ "code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
api "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert"
)
@@ -36,7 +38,7 @@ func TestAPIRepoTeams(t *testing.T) {
if assert.Len(t, teams, 2) {
assert.EqualValues(t, "Owners", teams[0].Name)
assert.False(t, teams[0].CanCreateOrgRepo)
- assert.EqualValues(t, []string{"repo.code", "repo.issues", "repo.pulls", "repo.releases", "repo.wiki", "repo.ext_wiki", "repo.ext_issues"}, teams[0].Units)
+ assert.True(t, util.IsEqualSlice(unit.AllUnitKeyNames(), teams[0].Units), fmt.Sprintf("%v == %v", unit.AllUnitKeyNames(), teams[0].Units))
assert.EqualValues(t, "owner", teams[0].Permission)
assert.EqualValues(t, "test_team", teams[1].Name)
diff --git a/integrations/api_team_test.go b/integrations/api_team_test.go
index da22d40479760..a622c63145f70 100644
--- a/integrations/api_team_test.go
+++ b/integrations/api_team_test.go
@@ -11,6 +11,7 @@ import (
"testing"
"code.gitea.io/gitea/models"
+ "code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/convert"
@@ -65,11 +66,12 @@ func TestAPITeam(t *testing.T) {
}
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/orgs/%s/teams?token=%s", org.Name, token), teamToCreate)
resp = session.MakeRequest(t, req, http.StatusCreated)
+ apiTeam = api.Team{}
DecodeJSON(t, resp, &apiTeam)
checkTeamResponse(t, &apiTeam, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories,
- teamToCreate.Permission, teamToCreate.Units)
+ teamToCreate.Permission, teamToCreate.Units, nil)
checkTeamBean(t, apiTeam.ID, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories,
- teamToCreate.Permission, teamToCreate.Units)
+ teamToCreate.Permission, teamToCreate.Units, nil)
teamID := apiTeam.ID
// Edit team.
@@ -85,30 +87,100 @@ func TestAPITeam(t *testing.T) {
req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d?token=%s", teamID, token), teamToEdit)
resp = session.MakeRequest(t, req, http.StatusOK)
+ apiTeam = api.Team{}
DecodeJSON(t, resp, &apiTeam)
checkTeamResponse(t, &apiTeam, teamToEdit.Name, *teamToEdit.Description, *teamToEdit.IncludesAllRepositories,
- teamToEdit.Permission, teamToEdit.Units)
+ teamToEdit.Permission, unit.AllUnitKeyNames(), nil)
checkTeamBean(t, apiTeam.ID, teamToEdit.Name, *teamToEdit.Description, *teamToEdit.IncludesAllRepositories,
- teamToEdit.Permission, teamToEdit.Units)
+ teamToEdit.Permission, unit.AllUnitKeyNames(), nil)
// Edit team Description only
editDescription = "first team"
teamToEditDesc := api.EditTeamOption{Description: &editDescription}
req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d?token=%s", teamID, token), teamToEditDesc)
resp = session.MakeRequest(t, req, http.StatusOK)
+ apiTeam = api.Team{}
DecodeJSON(t, resp, &apiTeam)
checkTeamResponse(t, &apiTeam, teamToEdit.Name, *teamToEditDesc.Description, *teamToEdit.IncludesAllRepositories,
- teamToEdit.Permission, teamToEdit.Units)
+ teamToEdit.Permission, unit.AllUnitKeyNames(), nil)
checkTeamBean(t, apiTeam.ID, teamToEdit.Name, *teamToEditDesc.Description, *teamToEdit.IncludesAllRepositories,
- teamToEdit.Permission, teamToEdit.Units)
+ teamToEdit.Permission, unit.AllUnitKeyNames(), nil)
// Read team.
teamRead := unittest.AssertExistsAndLoadBean(t, &models.Team{ID: teamID}).(*models.Team)
+ assert.NoError(t, teamRead.GetUnits())
req = NewRequestf(t, "GET", "/api/v1/teams/%d?token="+token, teamID)
resp = session.MakeRequest(t, req, http.StatusOK)
+ apiTeam = api.Team{}
DecodeJSON(t, resp, &apiTeam)
checkTeamResponse(t, &apiTeam, teamRead.Name, *teamToEditDesc.Description, teamRead.IncludesAllRepositories,
- teamRead.Authorize.String(), teamRead.GetUnitNames())
+ teamRead.AccessMode.String(), teamRead.GetUnitNames(), teamRead.GetUnitsMap())
+
+ // Delete team.
+ req = NewRequestf(t, "DELETE", "/api/v1/teams/%d?token="+token, teamID)
+ session.MakeRequest(t, req, http.StatusNoContent)
+ unittest.AssertNotExistsBean(t, &models.Team{ID: teamID})
+
+ // create team again via UnitsMap
+ // Create team.
+ teamToCreate = &api.CreateTeamOption{
+ Name: "team2",
+ Description: "team two",
+ IncludesAllRepositories: true,
+ Permission: "write",
+ UnitsMap: map[string]string{"repo.code": "read", "repo.issues": "write", "repo.wiki": "none"},
+ }
+ req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/orgs/%s/teams?token=%s", org.Name, token), teamToCreate)
+ resp = session.MakeRequest(t, req, http.StatusCreated)
+ apiTeam = api.Team{}
+ DecodeJSON(t, resp, &apiTeam)
+ checkTeamResponse(t, &apiTeam, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories,
+ "read", nil, teamToCreate.UnitsMap)
+ checkTeamBean(t, apiTeam.ID, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories,
+ "read", nil, teamToCreate.UnitsMap)
+ teamID = apiTeam.ID
+
+ // Edit team.
+ editDescription = "team 1"
+ editFalse = false
+ teamToEdit = &api.EditTeamOption{
+ Name: "teamtwo",
+ Description: &editDescription,
+ Permission: "write",
+ IncludesAllRepositories: &editFalse,
+ UnitsMap: map[string]string{"repo.code": "read", "repo.pulls": "read", "repo.releases": "write"},
+ }
+
+ req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d?token=%s", teamID, token), teamToEdit)
+ resp = session.MakeRequest(t, req, http.StatusOK)
+ apiTeam = api.Team{}
+ DecodeJSON(t, resp, &apiTeam)
+ checkTeamResponse(t, &apiTeam, teamToEdit.Name, *teamToEdit.Description, *teamToEdit.IncludesAllRepositories,
+ "read", nil, teamToEdit.UnitsMap)
+ checkTeamBean(t, apiTeam.ID, teamToEdit.Name, *teamToEdit.Description, *teamToEdit.IncludesAllRepositories,
+ "read", nil, teamToEdit.UnitsMap)
+
+ // Edit team Description only
+ editDescription = "second team"
+ teamToEditDesc = api.EditTeamOption{Description: &editDescription}
+ req = NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/teams/%d?token=%s", teamID, token), teamToEditDesc)
+ resp = session.MakeRequest(t, req, http.StatusOK)
+ apiTeam = api.Team{}
+ DecodeJSON(t, resp, &apiTeam)
+ checkTeamResponse(t, &apiTeam, teamToEdit.Name, *teamToEditDesc.Description, *teamToEdit.IncludesAllRepositories,
+ "read", nil, teamToEdit.UnitsMap)
+ checkTeamBean(t, apiTeam.ID, teamToEdit.Name, *teamToEditDesc.Description, *teamToEdit.IncludesAllRepositories,
+ "read", nil, teamToEdit.UnitsMap)
+
+ // Read team.
+ teamRead = unittest.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)
+ apiTeam = api.Team{}
+ DecodeJSON(t, resp, &apiTeam)
+ assert.NoError(t, teamRead.GetUnits())
+ checkTeamResponse(t, &apiTeam, teamRead.Name, *teamToEditDesc.Description, teamRead.IncludesAllRepositories,
+ teamRead.AccessMode.String(), teamRead.GetUnitNames(), teamRead.GetUnitsMap())
// Delete team.
req = NewRequestf(t, "DELETE", "/api/v1/teams/%d?token="+token, teamID)
@@ -116,20 +188,27 @@ func TestAPITeam(t *testing.T) {
unittest.AssertNotExistsBean(t, &models.Team{ID: teamID})
}
-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 checkTeamResponse(t *testing.T, apiTeam *api.Team, name, description string, includesAllRepositories bool, permission string, units []string, unitsMap map[string]string) {
+ t.Run(name+description, func(t *testing.T) {
+ 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")
+ if units != nil {
+ sort.StringSlice(units).Sort()
+ sort.StringSlice(apiTeam.Units).Sort()
+ assert.EqualValues(t, units, apiTeam.Units, "units")
+ }
+ if unitsMap != nil {
+ assert.EqualValues(t, unitsMap, apiTeam.UnitsMap, "unitsMap")
+ }
+ })
}
-func checkTeamBean(t *testing.T, id int64, name, description string, includesAllRepositories bool, permission string, units []string) {
+func checkTeamBean(t *testing.T, id int64, name, description string, includesAllRepositories bool, permission string, units []string, unitsMap map[string]string) {
team := unittest.AssertExistsAndLoadBean(t, &models.Team{ID: id}).(*models.Team)
assert.NoError(t, team.GetUnits(), "GetUnits")
- checkTeamResponse(t, convert.ToTeam(team), name, description, includesAllRepositories, permission, units)
+ checkTeamResponse(t, convert.ToTeam(team), name, description, includesAllRepositories, permission, units, unitsMap)
}
type TeamSearchResults struct {
@@ -162,5 +241,4 @@ func TestAPITeamSearch(t *testing.T) {
req = NewRequestf(t, "GET", "/api/v1/orgs/%s/teams/search?q=%s", org.Name, "team")
req.Header.Add("X-Csrf-Token", csrf)
session.MakeRequest(t, req, http.StatusForbidden)
-
}
diff --git a/integrations/dump_restore_test.go b/integrations/dump_restore_test.go
new file mode 100644
index 0000000000000..6fe5d8fe6a86c
--- /dev/null
+++ b/integrations/dump_restore_test.go
@@ -0,0 +1,104 @@
+// Copyright 2022 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 integrations
+
+import (
+ "context"
+ "net/url"
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+
+ repo_model "code.gitea.io/gitea/models/repo"
+ "code.gitea.io/gitea/models/unittest"
+ user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/structs"
+ "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/migrations"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestDumpRestore(t *testing.T) {
+ onGiteaRun(t, func(t *testing.T, u *url.URL) {
+ AllowLocalNetworks := setting.Migrations.AllowLocalNetworks
+ setting.Migrations.AllowLocalNetworks = true
+ AppVer := setting.AppVer
+ // Gitea SDK (go-sdk) need to parse the AppVer from server response, so we must set it to a valid version string.
+ setting.AppVer = "1.16.0"
+ defer func() {
+ setting.Migrations.AllowLocalNetworks = AllowLocalNetworks
+ setting.AppVer = AppVer
+ }()
+
+ assert.NoError(t, migrations.Init())
+
+ reponame := "repo1"
+
+ basePath, err := os.MkdirTemp("", reponame)
+ assert.NoError(t, err)
+ defer util.RemoveAll(basePath)
+
+ repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: reponame}).(*repo_model.Repository)
+ repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}).(*user_model.User)
+ session := loginUser(t, repoOwner.Name)
+ token := getTokenForLoggedInUser(t, session)
+
+ //
+ // Phase 1: dump repo1 from the Gitea instance to the filesystem
+ //
+
+ ctx := context.Background()
+ var opts = migrations.MigrateOptions{
+ GitServiceType: structs.GiteaService,
+ Issues: true,
+ Comments: true,
+ AuthToken: token,
+ CloneAddr: repo.CloneLink().HTTPS,
+ RepoName: reponame,
+ }
+ err = migrations.DumpRepository(ctx, basePath, repoOwner.Name, opts)
+ assert.NoError(t, err)
+
+ //
+ // Verify desired side effects of the dump
+ //
+ d := filepath.Join(basePath, repo.OwnerName, repo.Name)
+ for _, f := range []string{"repo.yml", "topic.yml", "issue.yml"} {
+ assert.FileExists(t, filepath.Join(d, f))
+ }
+
+ //
+ // Phase 2: restore from the filesystem to the Gitea instance in restoredrepo
+ //
+
+ newreponame := "restoredrepo"
+ err = migrations.RestoreRepository(ctx, d, repo.OwnerName, newreponame, []string{"issues", "comments"})
+ assert.NoError(t, err)
+
+ newrepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: newreponame}).(*repo_model.Repository)
+
+ //
+ // Phase 3: dump restoredrepo from the Gitea instance to the filesystem
+ //
+ opts.RepoName = newreponame
+ opts.CloneAddr = newrepo.CloneLink().HTTPS
+ err = migrations.DumpRepository(ctx, basePath, repoOwner.Name, opts)
+ assert.NoError(t, err)
+
+ //
+ // Verify the dump of restoredrepo is the same as the dump of repo1
+ //
+ newd := filepath.Join(basePath, newrepo.OwnerName, newrepo.Name)
+ beforeBytes, err := os.ReadFile(filepath.Join(d, "repo.yml"))
+ assert.NoError(t, err)
+ before := strings.ReplaceAll(string(beforeBytes), reponame, newreponame)
+ after, err := os.ReadFile(filepath.Join(newd, "repo.yml"))
+ assert.NoError(t, err)
+ assert.EqualValues(t, before, string(after))
+ })
+}
diff --git a/integrations/org_test.go b/integrations/org_test.go
index e94e4ea74c1c3..794475a9245d8 100644
--- a/integrations/org_test.go
+++ b/integrations/org_test.go
@@ -156,10 +156,10 @@ func TestOrgRestrictedUser(t *testing.T) {
resp := adminSession.MakeRequest(t, req, http.StatusCreated)
DecodeJSON(t, resp, &apiTeam)
checkTeamResponse(t, &apiTeam, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories,
- teamToCreate.Permission, teamToCreate.Units)
+ teamToCreate.Permission, teamToCreate.Units, nil)
checkTeamBean(t, apiTeam.ID, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories,
- teamToCreate.Permission, teamToCreate.Units)
- //teamID := apiTeam.ID
+ teamToCreate.Permission, teamToCreate.Units, nil)
+ // teamID := apiTeam.ID
// Now we need to add the restricted user to the team
req = NewRequest(t, "PUT",
@@ -172,5 +172,4 @@ func TestOrgRestrictedUser(t *testing.T) {
req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s", orgName, repoName))
restrictedSession.MakeRequest(t, req, http.StatusOK)
-
}
diff --git a/models/access.go b/models/access.go
index 6a97bcffcf7b5..48b65c2c0f605 100644
--- a/models/access.go
+++ b/models/access.go
@@ -162,7 +162,7 @@ func recalculateTeamAccesses(ctx context.Context, repo *repo_model.Repository, i
// Owner team gets owner access, and skip for teams that do not
// have relations with repository.
if t.IsOwnerTeam() {
- t.Authorize = perm.AccessModeOwner
+ t.AccessMode = perm.AccessModeOwner
} else if !t.hasRepository(e, repo.ID) {
continue
}
@@ -171,7 +171,7 @@ func recalculateTeamAccesses(ctx context.Context, repo *repo_model.Repository, i
return fmt.Errorf("getMembers '%d': %v", t.ID, err)
}
for _, m := range t.Members {
- updateUserAccess(accessMap, m, t.Authorize)
+ updateUserAccess(accessMap, m, t.AccessMode)
}
}
@@ -210,10 +210,10 @@ func recalculateUserAccess(ctx context.Context, repo *repo_model.Repository, uid
for _, t := range teams {
if t.IsOwnerTeam() {
- t.Authorize = perm.AccessModeOwner
+ t.AccessMode = perm.AccessModeOwner
}
- accessMode = maxAccessMode(accessMode, t.Authorize)
+ accessMode = maxAccessMode(accessMode, t.AccessMode)
}
}
diff --git a/models/fixtures/team_unit.yml b/models/fixtures/team_unit.yml
index 943745c000f9b..66f0d22efdfe5 100644
--- a/models/fixtures/team_unit.yml
+++ b/models/fixtures/team_unit.yml
@@ -2,223 +2,268 @@
id: 1
team_id: 1
type: 1
+ access_mode: 4
-
id: 2
team_id: 1
type: 2
+ access_mode: 4
-
id: 3
team_id: 1
type: 3
+ access_mode: 4
-
id: 4
team_id: 1
type: 4
+ access_mode: 4
-
id: 5
team_id: 1
type: 5
+ access_mode: 4
-
id: 6
team_id: 1
type: 6
+ access_mode: 4
-
id: 7
team_id: 1
type: 7
+ access_mode: 4
-
id: 8
team_id: 2
type: 1
+ access_mode: 2
-
id: 9
team_id: 2
type: 2
+ access_mode: 2
-
id: 10
team_id: 2
type: 3
+ access_mode: 2
-
id: 11
team_id: 2
type: 4
+ access_mode: 2
-
id: 12
team_id: 2
type: 5
+ access_mode: 2
-
id: 13
team_id: 2
type: 6
+ access_mode: 2
-
id: 14
team_id: 2
type: 7
+ access_mode: 2
-
id: 15
team_id: 3
type: 1
+ access_mode: 4
-
id: 16
team_id: 3
type: 2
+ access_mode: 4
-
id: 17
team_id: 3
type: 3
+ access_mode: 4
-
id: 18
team_id: 3
type: 4
+ access_mode: 4
-
id: 19
team_id: 3
type: 5
+ access_mode: 4
-
id: 20
team_id: 3
type: 6
+ access_mode: 4
-
id: 21
team_id: 3
type: 7
+ access_mode: 4
-
id: 22
team_id: 4
type: 1
+ access_mode: 4
-
id: 23
team_id: 4
type: 2
+ access_mode: 4
-
id: 24
team_id: 4
type: 3
+ access_mode: 4
-
id: 25
team_id: 4
type: 4
+ access_mode: 4
-
id: 26
team_id: 4
type: 5
+ access_mode: 4
-
id: 27
team_id: 4
type: 6
+ access_mode: 4
-
id: 28
team_id: 4
type: 7
+ access_mode: 4
-
id: 29
team_id: 5
type: 1
+ access_mode: 4
-
id: 30
team_id: 5
type: 2
+ access_mode: 4
-
id: 31
team_id: 5
type: 3
+ access_mode: 4
-
id: 32
team_id: 5
type: 4
+ access_mode: 4
-
id: 33
team_id: 5
type: 5
+ access_mode: 4
-
id: 34
team_id: 5
type: 6
+ access_mode: 4
-
id: 35
team_id: 5
type: 7
+ access_mode: 4
-
id: 36
team_id: 6
type: 1
+ access_mode: 4
-
id: 37
team_id: 6
type: 2
+ access_mode: 4
-
id: 38
team_id: 6
type: 3
+ access_mode: 4
-
id: 39
team_id: 6
type: 4
+ access_mode: 4
-
id: 40
team_id: 6
type: 5
+ access_mode: 4
-
id: 41
team_id: 6
type: 6
+ access_mode: 4
-
id: 42
team_id: 6
type: 7
+ access_mode: 4
-
id: 43
team_id: 7
type: 2 # issues
+ access_mode: 2
-
id: 44
team_id: 8
type: 2 # issues
+ access_mode: 2
-
id: 45
team_id: 9
- type: 1 # code
\ No newline at end of file
+ type: 1 # code
+ access_mode: 1
diff --git a/models/issue.go b/models/issue.go
index f0040fbbc1af4..108d9b217afd8 100644
--- a/models/issue.go
+++ b/models/issue.go
@@ -1350,8 +1350,8 @@ func (opts *IssuesOptions) setupSession(sess *xorm.Session) {
// issuePullAccessibleRepoCond userID must not be zero, this condition require join repository table
func issuePullAccessibleRepoCond(repoIDstr string, userID int64, org *Organization, team *Team, isPull bool) builder.Cond {
- var cond = builder.NewCond()
- var unitType = unit.TypeIssues
+ cond := builder.NewCond()
+ unitType := unit.TypeIssues
if isPull {
unitType = unit.TypePullRequests
}
@@ -2147,7 +2147,7 @@ func (issue *Issue) ResolveMentionsByVisibility(ctx context.Context, doer *user_
unittype = unit.TypePullRequests
}
for _, team := range teams {
- if team.Authorize >= perm.AccessModeOwner {
+ if team.AccessMode >= perm.AccessModeAdmin {
checked = append(checked, team.ID)
resolved[issue.Repo.Owner.LowerName+"/"+team.LowerName] = true
continue
diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go
index 4b720c3f02a4f..9423e5c5f6954 100644
--- a/models/migrations/migrations.go
+++ b/models/migrations/migrations.go
@@ -60,7 +60,6 @@ type Version struct {
// If you want to "retire" a migration, remove it from the top of the list and
// update minDBVersion accordingly
var migrations = []Migration{
-
// Gitea 1.5.0 ends at v69
// v70 -> v71
@@ -365,6 +364,8 @@ var migrations = []Migration{
NewMigration("Add key is verified to ssh key", addSSHKeyIsVerified),
// v205 -> v206
NewMigration("Migrate to higher varchar on user struct", migrateUserPasswordSalt),
+ // v206 -> v207
+ NewMigration("Add authorize column to team_unit table", addAuthorizeColForTeamUnit),
}
// GetCurrentDBVersion returns the current db version
diff --git a/models/migrations/v206.go b/models/migrations/v206.go
new file mode 100644
index 0000000000000..c6a5dc811c59b
--- /dev/null
+++ b/models/migrations/v206.go
@@ -0,0 +1,29 @@
+// Copyright 2022 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 (
+ "fmt"
+
+ "xorm.io/xorm"
+)
+
+func addAuthorizeColForTeamUnit(x *xorm.Engine) error {
+ type TeamUnit struct {
+ ID int64 `xorm:"pk autoincr"`
+ OrgID int64 `xorm:"INDEX"`
+ TeamID int64 `xorm:"UNIQUE(s)"`
+ Type int `xorm:"UNIQUE(s)"`
+ AccessMode int
+ }
+
+ if err := x.Sync2(new(TeamUnit)); err != nil {
+ return fmt.Errorf("sync2: %v", err)
+ }
+
+ // migrate old permission
+ _, err := x.Exec("UPDATE team_unit SET access_mode = (SELECT authorize FROM team WHERE team.id = team_unit.team_id)")
+ return err
+}
diff --git a/models/org.go b/models/org.go
index c135bb9d3cc31..0ea2ce6886fd2 100644
--- a/models/org.go
+++ b/models/org.go
@@ -265,7 +265,7 @@ func CreateOrganization(org *Organization, owner *user_model.User) (err error) {
OrgID: org.ID,
LowerName: strings.ToLower(ownerTeamName),
Name: ownerTeamName,
- Authorize: perm.AccessModeOwner,
+ AccessMode: perm.AccessModeOwner,
NumMembers: 1,
IncludesAllRepositories: true,
CanCreateOrgRepo: true,
@@ -523,7 +523,7 @@ type FindOrgOptions struct {
}
func queryUserOrgIDs(userID int64, includePrivate bool) *builder.Builder {
- var cond = builder.Eq{"uid": userID}
+ cond := builder.Eq{"uid": userID}
if !includePrivate {
cond["is_public"] = true
}
@@ -531,7 +531,7 @@ func queryUserOrgIDs(userID int64, includePrivate bool) *builder.Builder {
}
func (opts FindOrgOptions) toConds() builder.Cond {
- var cond = builder.NewCond()
+ cond := builder.NewCond()
if opts.UserID > 0 {
cond = cond.And(builder.In("`user`.`id`", queryUserOrgIDs(opts.UserID, opts.IncludePrivate)))
}
diff --git a/models/org_team.go b/models/org_team.go
index 7eac0f7bc52fe..bce4afb0611b1 100644
--- a/models/org_team.go
+++ b/models/org_team.go
@@ -32,7 +32,7 @@ type Team struct {
LowerName string
Name string
Description string
- Authorize perm.AccessMode
+ AccessMode perm.AccessMode `xorm:"'authorize'"`
Repos []*repo_model.Repository `xorm:"-"`
Members []*user_model.User `xorm:"-"`
NumRepos int
@@ -126,7 +126,7 @@ func (t *Team) ColorFormat(s fmt.State) {
log.NewColoredIDValue(t.ID),
t.Name,
log.NewColoredIDValue(t.OrgID),
- t.Authorize)
+ t.AccessMode)
}
// GetUnits return a list of available units for a team
@@ -145,15 +145,29 @@ func (t *Team) getUnits(e db.Engine) (err error) {
// GetUnitNames returns the team units names
func (t *Team) GetUnitNames() (res []string) {
+ if t.AccessMode >= perm.AccessModeAdmin {
+ return unit.AllUnitKeyNames()
+ }
+
for _, u := range t.Units {
res = append(res, unit.Units[u.Type].NameKey)
}
return
}
-// HasWriteAccess returns true if team has at least write level access mode.
-func (t *Team) HasWriteAccess() bool {
- return t.Authorize >= perm.AccessModeWrite
+// GetUnitsMap returns the team units permissions
+func (t *Team) GetUnitsMap() map[string]string {
+ m := make(map[string]string)
+ if t.AccessMode >= perm.AccessModeAdmin {
+ for _, u := range unit.Units {
+ m[u.NameKey] = t.AccessMode.String()
+ }
+ } else {
+ for _, u := range t.Units {
+ m[u.Unit().NameKey] = u.AccessMode.String()
+ }
+ }
+ return m
}
// IsOwnerTeam returns true if team is owner team.
@@ -455,16 +469,25 @@ func (t *Team) UnitEnabled(tp unit.Type) bool {
}
func (t *Team) unitEnabled(e db.Engine, tp unit.Type) bool {
+ return t.unitAccessMode(e, tp) > perm.AccessModeNone
+}
+
+// UnitAccessMode returns if the team has the given unit type enabled
+func (t *Team) UnitAccessMode(tp unit.Type) perm.AccessMode {
+ return t.unitAccessMode(db.GetEngine(db.DefaultContext), tp)
+}
+
+func (t *Team) unitAccessMode(e db.Engine, tp unit.Type) perm.AccessMode {
if err := t.getUnits(e); err != nil {
log.Warn("Error loading team (ID: %d) units: %s", t.ID, err.Error())
}
for _, unit := range t.Units {
if unit.Type == tp {
- return true
+ return unit.AccessMode
}
}
- return false
+ return perm.AccessModeNone
}
// IsUsableTeamName tests if a name could be as team name
@@ -661,7 +684,7 @@ func UpdateTeam(t *Team, authChanged, includeAllChanged bool) (err error) {
Delete(new(TeamUnit)); err != nil {
return err
}
- if _, err = sess.Cols("org_id", "team_id", "type").Insert(&t.Units); err != nil {
+ if _, err = sess.Cols("org_id", "team_id", "type", "access_mode").Insert(&t.Units); err != nil {
return err
}
}
@@ -1033,10 +1056,11 @@ func GetTeamsWithAccessToRepo(orgID, repoID int64, mode perm.AccessMode) ([]*Tea
// TeamUnit describes all units of a repository
type TeamUnit struct {
- ID int64 `xorm:"pk autoincr"`
- OrgID int64 `xorm:"INDEX"`
- TeamID int64 `xorm:"UNIQUE(s)"`
- Type unit.Type `xorm:"UNIQUE(s)"`
+ ID int64 `xorm:"pk autoincr"`
+ OrgID int64 `xorm:"INDEX"`
+ TeamID int64 `xorm:"UNIQUE(s)"`
+ Type unit.Type `xorm:"UNIQUE(s)"`
+ AccessMode perm.AccessMode
}
// Unit returns Unit
diff --git a/models/org_team_test.go b/models/org_team_test.go
index 59b7b6d5a834c..aa62cc58e2d00 100644
--- a/models/org_team_test.go
+++ b/models/org_team_test.go
@@ -211,7 +211,7 @@ func TestUpdateTeam(t *testing.T) {
team.LowerName = "newname"
team.Name = "newName"
team.Description = strings.Repeat("A long description!", 100)
- team.Authorize = perm.AccessModeAdmin
+ team.AccessMode = perm.AccessModeAdmin
assert.NoError(t, UpdateTeam(team, true, false))
team = unittest.AssertExistsAndLoadBean(t, &Team{Name: "newName"}).(*Team)
diff --git a/models/perm/access_mode.go b/models/perm/access_mode.go
index f2c0a322a085f..dfa7f7b7524a1 100644
--- a/models/perm/access_mode.go
+++ b/models/perm/access_mode.go
@@ -51,11 +51,13 @@ func (mode AccessMode) ColorFormat(s fmt.State) {
// ParseAccessMode returns corresponding access mode to given permission string.
func ParseAccessMode(permission string) AccessMode {
switch permission {
+ case "read":
+ return AccessModeRead
case "write":
return AccessModeWrite
case "admin":
return AccessModeAdmin
default:
- return AccessModeRead
+ return AccessModeNone
}
}
diff --git a/models/repo_permission.go b/models/repo_permission.go
index 40b63aa804313..4e5cbfd55807e 100644
--- a/models/repo_permission.go
+++ b/models/repo_permission.go
@@ -239,7 +239,7 @@ func getUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
// if user in an owner team
for _, team := range teams {
- if team.Authorize >= perm_model.AccessModeOwner {
+ if team.AccessMode >= perm_model.AccessModeAdmin {
perm.AccessMode = perm_model.AccessModeOwner
perm.UnitsMode = nil
return
@@ -249,10 +249,11 @@ func getUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
for _, u := range repo.Units {
var found bool
for _, team := range teams {
- if team.unitEnabled(e, u.Type) {
+ teamMode := team.unitAccessMode(e, u.Type)
+ if teamMode > perm_model.AccessModeNone {
m := perm.UnitsMode[u.Type]
- if m < team.Authorize {
- perm.UnitsMode[u.Type] = team.Authorize
+ if m < teamMode {
+ perm.UnitsMode[u.Type] = teamMode
}
found = true
}
@@ -324,7 +325,7 @@ func isUserRepoAdmin(e db.Engine, repo *repo_model.Repository, user *user_model.
}
for _, team := range teams {
- if team.Authorize >= perm_model.AccessModeAdmin {
+ if team.AccessMode >= perm_model.AccessModeAdmin {
return true, nil
}
}
diff --git a/models/review.go b/models/review.go
index eeb33611ceb15..023f98c3eace5 100644
--- a/models/review.go
+++ b/models/review.go
@@ -280,7 +280,7 @@ func isOfficialReviewerTeam(ctx context.Context, issue *Issue, team *Team) (bool
}
if !pr.ProtectedBranch.EnableApprovalsWhitelist {
- return team.Authorize >= perm.AccessModeWrite, nil
+ return team.UnitAccessMode(unit.TypeCode) >= perm.AccessModeWrite, nil
}
return base.Int64sContains(pr.ProtectedBranch.ApprovalsWhitelistTeamIDs, team.ID), nil
diff --git a/models/unit/unit.go b/models/unit/unit.go
index 0af4640b7a4b4..b05f34b64cca6 100644
--- a/models/unit/unit.go
+++ b/models/unit/unit.go
@@ -8,6 +8,7 @@ import (
"fmt"
"strings"
+ "code.gitea.io/gitea/models/perm"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
)
@@ -17,14 +18,15 @@ type Type int
// Enumerate all the unit types
const (
- TypeCode Type = iota + 1 // 1 code
- TypeIssues // 2 issues
- TypePullRequests // 3 PRs
- TypeReleases // 4 Releases
- TypeWiki // 5 Wiki
- TypeExternalWiki // 6 ExternalWiki
- TypeExternalTracker // 7 ExternalTracker
- TypeProjects // 8 Kanban board
+ TypeInvalid Type = iota // 0 invalid
+ TypeCode // 1 code
+ TypeIssues // 2 issues
+ TypePullRequests // 3 PRs
+ TypeReleases // 4 Releases
+ TypeWiki // 5 Wiki
+ TypeExternalWiki // 6 ExternalWiki
+ TypeExternalTracker // 7 ExternalTracker
+ TypeProjects // 8 Kanban board
)
// Value returns integer value for unit type
@@ -170,11 +172,12 @@ func (u *Type) CanBeDefault() bool {
// Unit is a section of one repository
type Unit struct {
- Type Type
- NameKey string
- URI string
- DescKey string
- Idx int
+ Type Type
+ NameKey string
+ URI string
+ DescKey string
+ Idx int
+ MaxAccessMode perm.AccessMode // The max access mode of the unit. i.e. Read means this unit can only be read.
}
// CanDisable returns if this unit could be disabled.
@@ -198,6 +201,7 @@ var (
"/",
"repo.code.desc",
0,
+ perm.AccessModeOwner,
}
UnitIssues = Unit{
@@ -206,6 +210,7 @@ var (
"/issues",
"repo.issues.desc",
1,
+ perm.AccessModeOwner,
}
UnitExternalTracker = Unit{
@@ -214,6 +219,7 @@ var (
"/issues",
"repo.ext_issues.desc",
1,
+ perm.AccessModeRead,
}
UnitPullRequests = Unit{
@@ -222,6 +228,7 @@ var (
"/pulls",
"repo.pulls.desc",
2,
+ perm.AccessModeOwner,
}
UnitReleases = Unit{
@@ -230,6 +237,7 @@ var (
"/releases",
"repo.releases.desc",
3,
+ perm.AccessModeOwner,
}
UnitWiki = Unit{
@@ -238,6 +246,7 @@ var (
"/wiki",
"repo.wiki.desc",
4,
+ perm.AccessModeOwner,
}
UnitExternalWiki = Unit{
@@ -246,6 +255,7 @@ var (
"/wiki",
"repo.ext_wiki.desc",
4,
+ perm.AccessModeRead,
}
UnitProjects = Unit{
@@ -254,6 +264,7 @@ var (
"/projects",
"repo.projects.desc",
5,
+ perm.AccessModeOwner,
}
// Units contains all the units
@@ -269,15 +280,51 @@ var (
}
)
-// FindUnitTypes give the unit key name and return unit
+// FindUnitTypes give the unit key names and return unit
func FindUnitTypes(nameKeys ...string) (res []Type) {
for _, key := range nameKeys {
+ var found bool
for t, u := range Units {
if strings.EqualFold(key, u.NameKey) {
res = append(res, t)
+ found = true
break
}
}
+ if !found {
+ res = append(res, TypeInvalid)
+ }
}
return
}
+
+// TypeFromKey give the unit key name and return unit
+func TypeFromKey(nameKey string) Type {
+ for t, u := range Units {
+ if strings.EqualFold(nameKey, u.NameKey) {
+ return t
+ }
+ }
+ return TypeInvalid
+}
+
+// AllUnitKeyNames returns all unit key names
+func AllUnitKeyNames() []string {
+ res := make([]string, 0, len(Units))
+ for _, u := range Units {
+ res = append(res, u.NameKey)
+ }
+ return res
+}
+
+// MinUnitAccessMode returns the minial permission of the permission map
+func MinUnitAccessMode(unitsMap map[Type]perm.AccessMode) perm.AccessMode {
+ res := perm.AccessModeNone
+ for _, mode := range unitsMap {
+ // get the minial permission great than AccessModeNone except all are AccessModeNone
+ if mode > perm.AccessModeNone && (res == perm.AccessModeNone || mode < res) {
+ res = mode
+ }
+ }
+ return res
+}
diff --git a/modules/context/org.go b/modules/context/org.go
index eb81f6644c171..585a5fd762c6d 100644
--- a/modules/context/org.go
+++ b/modules/context/org.go
@@ -168,7 +168,7 @@ func HandleOrgAssignment(ctx *Context, args ...bool) {
return
}
- ctx.Org.IsTeamAdmin = ctx.Org.Team.IsOwnerTeam() || ctx.Org.Team.Authorize >= perm.AccessModeAdmin
+ ctx.Org.IsTeamAdmin = ctx.Org.Team.IsOwnerTeam() || ctx.Org.Team.AccessMode >= perm.AccessModeAdmin
ctx.Data["IsTeamAdmin"] = ctx.Org.IsTeamAdmin
if requireTeamAdmin && !ctx.Org.IsTeamAdmin {
ctx.NotFound("OrgAssignment", err)
diff --git a/modules/convert/convert.go b/modules/convert/convert.go
index f2b62a74bf7c9..41a044c6d74e2 100644
--- a/modules/convert/convert.go
+++ b/modules/convert/convert.go
@@ -306,8 +306,9 @@ func ToTeam(team *models.Team) *api.Team {
Description: team.Description,
IncludesAllRepositories: team.IncludesAllRepositories,
CanCreateOrgRepo: team.CanCreateOrgRepo,
- Permission: team.Authorize.String(),
+ Permission: team.AccessMode.String(),
Units: team.GetUnitNames(),
+ UnitsMap: team.GetUnitsMap(),
}
}
diff --git a/modules/repository/create_test.go b/modules/repository/create_test.go
index 18995f4ecd210..ed890ace43319 100644
--- a/modules/repository/create_test.go
+++ b/modules/repository/create_test.go
@@ -70,25 +70,25 @@ func TestIncludesAllRepositoriesTeams(t *testing.T) {
{
OrgID: org.ID,
Name: "team one",
- Authorize: perm.AccessModeRead,
+ AccessMode: perm.AccessModeRead,
IncludesAllRepositories: true,
},
{
OrgID: org.ID,
Name: "team 2",
- Authorize: perm.AccessModeRead,
+ AccessMode: perm.AccessModeRead,
IncludesAllRepositories: false,
},
{
OrgID: org.ID,
Name: "team three",
- Authorize: perm.AccessModeWrite,
+ AccessMode: perm.AccessModeWrite,
IncludesAllRepositories: true,
},
{
OrgID: org.ID,
Name: "team 4",
- Authorize: perm.AccessModeWrite,
+ AccessMode: perm.AccessModeWrite,
IncludesAllRepositories: false,
},
}
diff --git a/modules/structs/org_team.go b/modules/structs/org_team.go
index 3b2c5e78391e0..53e3fcf62da45 100644
--- a/modules/structs/org_team.go
+++ b/modules/structs/org_team.go
@@ -15,8 +15,11 @@ type Team struct {
// enum: none,read,write,admin,owner
Permission string `json:"permission"`
// example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.projects","repo.ext_wiki"]
- Units []string `json:"units"`
- CanCreateOrgRepo bool `json:"can_create_org_repo"`
+ // Deprecated: This variable should be replaced by UnitsMap and will be dropped in later versions.
+ Units []string `json:"units"`
+ // example: {"repo.code":"read","repo.issues":"write","repo.ext_issues":"none","repo.wiki":"admin","repo.pulls":"owner","repo.releases":"none","repo.projects":"none","repo.ext_wiki":"none"]
+ UnitsMap map[string]string `json:"units_map"`
+ CanCreateOrgRepo bool `json:"can_create_org_repo"`
}
// CreateTeamOption options for creating a team
@@ -28,8 +31,11 @@ type CreateTeamOption struct {
// enum: read,write,admin
Permission string `json:"permission"`
// example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.projects","repo.ext_wiki"]
- Units []string `json:"units"`
- CanCreateOrgRepo bool `json:"can_create_org_repo"`
+ // Deprecated: This variable should be replaced by UnitsMap and will be dropped in later versions.
+ Units []string `json:"units"`
+ // example: {"repo.code":"read","repo.issues":"write","repo.ext_issues":"none","repo.wiki":"admin","repo.pulls":"owner","repo.releases":"none","repo.projects":"none","repo.ext_wiki":"none"]
+ UnitsMap map[string]string `json:"units_map"`
+ CanCreateOrgRepo bool `json:"can_create_org_repo"`
}
// EditTeamOption options for editing a team
@@ -41,6 +47,9 @@ type EditTeamOption struct {
// enum: read,write,admin
Permission string `json:"permission"`
// example: ["repo.code","repo.issues","repo.ext_issues","repo.wiki","repo.pulls","repo.releases","repo.projects","repo.ext_wiki"]
- Units []string `json:"units"`
- CanCreateOrgRepo *bool `json:"can_create_org_repo"`
+ // Deprecated: This variable should be replaced by UnitsMap and will be dropped in later versions.
+ Units []string `json:"units"`
+ // example: {"repo.code":"read","repo.issues":"write","repo.ext_issues":"none","repo.wiki":"admin","repo.pulls":"owner","repo.releases":"none","repo.projects":"none","repo.ext_wiki":"none"]
+ UnitsMap map[string]string `json:"units_map"`
+ CanCreateOrgRepo *bool `json:"can_create_org_repo"`
}
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 9164d5ffdceda..7a3dbd50a812c 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -1099,7 +1099,7 @@ commits.signed_by_untrusted_user_unmatched = Signed by untrusted user who does n
commits.gpg_key_id = GPG Key ID
commits.ssh_key_fingerprint = SSH Key Fingerprint
-ext_issues = Ext. Issues
+ext_issues = Access to External Issues
ext_issues.desc = Link to an external issue tracker.
projects = Projects
@@ -1579,7 +1579,7 @@ signing.wont_sign.commitssigned = The merge will not be signed as all the associ
signing.wont_sign.approved = The merge will not be signed as the PR is not approved
signing.wont_sign.not_signed_in = You are not signed in
-ext_wiki = Ext. Wiki
+ext_wiki = Access to External Wiki
ext_wiki.desc = Link to an external wiki.
wiki = Wiki
@@ -2261,9 +2261,13 @@ teams.leave = Leave
teams.leave.detail = Leave %s?
teams.can_create_org_repo = Create repositories
teams.can_create_org_repo_helper = Members can create new repositories in organization. Creator will get administrator access to the new repository.
-teams.read_access = Read Access
+teams.none_access = No Access
+teams.none_access_helper = Members cannot view or do any other action on this unit.
+teams.general_access = General Access
+teams.general_access_helper = Members permissions will be decided by below permission table.
+teams.read_access = Read
teams.read_access_helper = Members can view and clone team repositories.
-teams.write_access = Write Access
+teams.write_access = Write
teams.write_access_helper = Members can read and push to team repositories.
teams.admin_access = Administrator Access
teams.admin_access_helper = Members can pull and push to team repositories and add collaborators to them.
@@ -2892,5 +2896,6 @@ error.probable_bad_signature = "WARNING! Although there is a key with this ID in
error.probable_bad_default_signature = "WARNING! Although the default key has this ID it does not verify this commit! This commit is SUSPICIOUS."
[units]
+unit = Unit
error.no_unit_allowed_repo = You are not allowed to access any section of this repository.
error.unit_not_allowed = You are not allowed to access this repository section.
diff --git a/routers/api/v1/org/team.go b/routers/api/v1/org/team.go
index d39125b0500e3..cc7a63af337ec 100644
--- a/routers/api/v1/org/team.go
+++ b/routers/api/v1/org/team.go
@@ -6,6 +6,7 @@
package org
import (
+ "errors"
"net/http"
"code.gitea.io/gitea/models"
@@ -50,7 +51,6 @@ func ListTeams(ctx *context.APIContext) {
ListOptions: utils.GetListOptions(ctx),
OrgID: ctx.Org.Organization.ID,
})
-
if err != nil {
ctx.Error(http.StatusInternalServerError, "LoadTeams", err)
return
@@ -112,6 +112,10 @@ func ListUserTeams(ctx *context.APIContext) {
apiOrg = convert.ToOrganization(org)
cache[teams[i].OrgID] = apiOrg
}
+ if err := teams[i].GetUnits(); err != nil {
+ ctx.Error(http.StatusInternalServerError, "teams[i].GetUnits()", err)
+ return
+ }
apiTeams[i] = convert.ToTeam(teams[i])
apiTeams[i].Organization = apiOrg
}
@@ -138,9 +142,45 @@ func GetTeam(ctx *context.APIContext) {
// "200":
// "$ref": "#/responses/Team"
+ if err := ctx.Org.Team.GetUnits(); err != nil {
+ ctx.Error(http.StatusInternalServerError, "team.GetUnits", err)
+ return
+ }
+
ctx.JSON(http.StatusOK, convert.ToTeam(ctx.Org.Team))
}
+func attachTeamUnits(team *models.Team, units []string) {
+ unitTypes := unit_model.FindUnitTypes(units...)
+ team.Units = make([]*models.TeamUnit, 0, len(units))
+ for _, tp := range unitTypes {
+ team.Units = append(team.Units, &models.TeamUnit{
+ OrgID: team.OrgID,
+ Type: tp,
+ AccessMode: team.AccessMode,
+ })
+ }
+}
+
+func convertUnitsMap(unitsMap map[string]string) map[unit_model.Type]perm.AccessMode {
+ res := make(map[unit_model.Type]perm.AccessMode, len(unitsMap))
+ for unitKey, p := range unitsMap {
+ res[unit_model.TypeFromKey(unitKey)] = perm.ParseAccessMode(p)
+ }
+ return res
+}
+
+func attachTeamUnitsMap(team *models.Team, unitsMap map[string]string) {
+ team.Units = make([]*models.TeamUnit, 0, len(unitsMap))
+ for unitKey, p := range unitsMap {
+ team.Units = append(team.Units, &models.TeamUnit{
+ OrgID: team.OrgID,
+ Type: unit_model.TypeFromKey(unitKey),
+ AccessMode: perm.ParseAccessMode(p),
+ })
+ }
+}
+
// CreateTeam api for create a team
func CreateTeam(ctx *context.APIContext) {
// swagger:operation POST /orgs/{org}/teams organization orgCreateTeam
@@ -166,26 +206,28 @@ func CreateTeam(ctx *context.APIContext) {
// "422":
// "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.CreateTeamOption)
+ p := perm.ParseAccessMode(form.Permission)
+ if p < perm.AccessModeAdmin && len(form.UnitsMap) > 0 {
+ p = unit_model.MinUnitAccessMode(convertUnitsMap(form.UnitsMap))
+ }
team := &models.Team{
OrgID: ctx.Org.Organization.ID,
Name: form.Name,
Description: form.Description,
IncludesAllRepositories: form.IncludesAllRepositories,
CanCreateOrgRepo: form.CanCreateOrgRepo,
- Authorize: perm.ParseAccessMode(form.Permission),
+ AccessMode: p,
}
- unitTypes := unit_model.FindUnitTypes(form.Units...)
-
- if team.Authorize < perm.AccessModeOwner {
- var units = make([]*models.TeamUnit, 0, len(form.Units))
- for _, tp := range unitTypes {
- units = append(units, &models.TeamUnit{
- OrgID: ctx.Org.Organization.ID,
- Type: tp,
- })
+ if team.AccessMode < perm.AccessModeAdmin {
+ if len(form.UnitsMap) > 0 {
+ attachTeamUnitsMap(team, form.UnitsMap)
+ } else if len(form.Units) > 0 {
+ attachTeamUnits(team, form.Units)
+ } else {
+ ctx.Error(http.StatusInternalServerError, "getTeamUnits", errors.New("units permission should not be empty"))
+ return
}
- team.Units = units
}
if err := models.NewTeam(team); err != nil {
@@ -224,7 +266,6 @@ func EditTeam(ctx *context.APIContext) {
// "$ref": "#/responses/Team"
form := web.GetForm(ctx).(*api.EditTeamOption)
-
team := ctx.Org.Team
if err := team.GetUnits(); err != nil {
ctx.InternalServerError(err)
@@ -247,11 +288,14 @@ func EditTeam(ctx *context.APIContext) {
isIncludeAllChanged := false
if !team.IsOwnerTeam() && len(form.Permission) != 0 {
// Validate permission level.
- auth := perm.ParseAccessMode(form.Permission)
+ p := perm.ParseAccessMode(form.Permission)
+ if p < perm.AccessModeAdmin && len(form.UnitsMap) > 0 {
+ p = unit_model.MinUnitAccessMode(convertUnitsMap(form.UnitsMap))
+ }
- if team.Authorize != auth {
+ if team.AccessMode != p {
isAuthChanged = true
- team.Authorize = auth
+ team.AccessMode = p
}
if form.IncludesAllRepositories != nil {
@@ -260,17 +304,11 @@ func EditTeam(ctx *context.APIContext) {
}
}
- if team.Authorize < perm.AccessModeOwner {
- if len(form.Units) > 0 {
- var units = make([]*models.TeamUnit, 0, len(form.Units))
- unitTypes := unit_model.FindUnitTypes(form.Units...)
- for _, tp := range unitTypes {
- units = append(units, &models.TeamUnit{
- OrgID: ctx.Org.Team.OrgID,
- Type: tp,
- })
- }
- team.Units = units
+ if team.AccessMode < perm.AccessModeAdmin {
+ if len(form.UnitsMap) > 0 {
+ attachTeamUnitsMap(team, form.UnitsMap)
+ } else if len(form.Units) > 0 {
+ attachTeamUnits(team, form.Units)
}
}
@@ -706,5 +744,4 @@ func SearchTeam(ctx *context.APIContext) {
"ok": true,
"data": apiTeams,
})
-
}
diff --git a/routers/web/org/teams.go b/routers/web/org/teams.go
index 40fba5cd09a4f..732f24b22c27d 100644
--- a/routers/web/org/teams.go
+++ b/routers/web/org/teams.go
@@ -9,6 +9,7 @@ import (
"net/http"
"net/url"
"path"
+ "strconv"
"strings"
"code.gitea.io/gitea/models"
@@ -224,35 +225,57 @@ func NewTeam(ctx *context.Context) {
ctx.HTML(http.StatusOK, tplTeamNew)
}
+func getUnitPerms(forms url.Values) 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)
+ }
+ }
+ }
+ return unitPerms
+}
+
// NewTeamPost response for create new team
func NewTeamPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.CreateTeamForm)
- ctx.Data["Title"] = ctx.Org.Organization.FullName
- ctx.Data["PageIsOrgTeams"] = true
- ctx.Data["PageIsOrgTeamsNew"] = true
- ctx.Data["Units"] = unit_model.Units
- var includesAllRepositories = form.RepoAccess == "all"
+ includesAllRepositories := form.RepoAccess == "all"
+ unitPerms := getUnitPerms(ctx.Req.Form)
+ p := perm.ParseAccessMode(form.Permission)
+ 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.
+ p = unit_model.MinUnitAccessMode(unitPerms)
+ }
t := &models.Team{
OrgID: ctx.Org.Organization.ID,
Name: form.TeamName,
Description: form.Description,
- Authorize: perm.ParseAccessMode(form.Permission),
+ AccessMode: p,
IncludesAllRepositories: includesAllRepositories,
CanCreateOrgRepo: form.CanCreateOrgRepo,
}
- if t.Authorize < perm.AccessModeOwner {
- var units = make([]*models.TeamUnit, 0, len(form.Units))
- for _, tp := range form.Units {
+ if t.AccessMode < perm.AccessModeAdmin {
+ units := make([]*models.TeamUnit, 0, len(unitPerms))
+ for tp, perm := range unitPerms {
units = append(units, &models.TeamUnit{
- OrgID: ctx.Org.Organization.ID,
- Type: tp,
+ OrgID: ctx.Org.Organization.ID,
+ Type: tp,
+ AccessMode: perm,
})
}
t.Units = units
}
+ ctx.Data["Title"] = ctx.Org.Organization.FullName
+ ctx.Data["PageIsOrgTeams"] = true
+ ctx.Data["PageIsOrgTeamsNew"] = true
+ ctx.Data["Units"] = unit_model.Units
ctx.Data["Team"] = t
if ctx.HasError() {
@@ -260,7 +283,7 @@ func NewTeamPost(ctx *context.Context) {
return
}
- if t.Authorize < perm.AccessModeAdmin && len(form.Units) == 0 {
+ if t.AccessMode < perm.AccessModeAdmin && len(unitPerms) == 0 {
ctx.RenderWithErr(ctx.Tr("form.team_no_units_error"), tplTeamNew, &form)
return
}
@@ -317,22 +340,29 @@ 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)
+ isAuthChanged := false
+ isIncludeAllChanged := false
+ includesAllRepositories := form.RepoAccess == "all"
+
ctx.Data["Title"] = ctx.Org.Organization.FullName
ctx.Data["PageIsOrgTeams"] = true
ctx.Data["Team"] = t
ctx.Data["Units"] = unit_model.Units
- isAuthChanged := false
- isIncludeAllChanged := false
- var includesAllRepositories = form.RepoAccess == "all"
if !t.IsOwnerTeam() {
// Validate permission level.
- auth := perm.ParseAccessMode(form.Permission)
+ 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.Authorize != auth {
+ if t.AccessMode != newAccessMode {
isAuthChanged = true
- t.Authorize = auth
+ t.AccessMode = newAccessMode
}
if t.IncludesAllRepositories != includesAllRepositories {
@@ -341,17 +371,17 @@ func EditTeamPost(ctx *context.Context) {
}
}
t.Description = form.Description
- if t.Authorize < perm.AccessModeOwner {
- var units = make([]models.TeamUnit, 0, len(form.Units))
- for _, tp := range form.Units {
+ if t.AccessMode < perm.AccessModeAdmin {
+ units := make([]models.TeamUnit, 0, len(unitPerms))
+ for tp, perm := range unitPerms {
units = append(units, models.TeamUnit{
- OrgID: t.OrgID,
- TeamID: t.ID,
- Type: tp,
+ OrgID: t.OrgID,
+ TeamID: t.ID,
+ Type: tp,
+ AccessMode: perm,
})
}
- err := models.UpdateTeamUnits(t, units)
- if err != nil {
+ if err := models.UpdateTeamUnits(t, units); err != nil {
ctx.Error(http.StatusInternalServerError, "LoadIssue", err.Error())
return
}
@@ -363,7 +393,7 @@ func EditTeamPost(ctx *context.Context) {
return
}
- if t.Authorize < perm.AccessModeAdmin && len(form.Units) == 0 {
+ if t.AccessMode < perm.AccessModeAdmin && len(unitPerms) == 0 {
ctx.RenderWithErr(ctx.Tr("form.team_no_units_error"), tplTeamNew, &form)
return
}
diff --git a/services/forms/org.go b/services/forms/org.go
index 7c8f17f95ee60..dec2dbfa6555a 100644
--- a/services/forms/org.go
+++ b/services/forms/org.go
@@ -8,7 +8,6 @@ package forms
import (
"net/http"
- "code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/web/middleware"
@@ -66,7 +65,6 @@ type CreateTeamForm struct {
TeamName string `binding:"Required;AlphaDashDot;MaxSize(30)"`
Description string `binding:"MaxSize(255)"`
Permission string
- Units []unit.Type
RepoAccess string
CanCreateOrgRepo bool
}
diff --git a/templates/org/team/new.tmpl b/templates/org/team/new.tmpl
index 783e025ebdc08..1cf2dd0236ec1 100644
--- a/templates/org/team/new.tmpl
+++ b/templates/org/team/new.tmpl
@@ -56,21 +56,14 @@