Browse Source
Restrict permission check on repositories and fix some problems (#5314)
Restrict permission check on repositories and fix some problems (#5314)
* fix units permission problems * fix some bugs and merge LoadUnits to repoAssignment * refactor permission struct and add some copyright heads * remove unused codes * fix routes units check * improve permission check * add unit tests for permission * fix typo * fix tests * fix some routes * fix api permission check * improve permission check * fix some permission check * fix tests * fix tests * improve some permission check * fix some permission check * refactor AccessLevel * fix bug * fix tests * fix tests * fix tests * fix AccessLevel * rename CanAccess * fix tests * fix comment * fix bug * add missing unit for test repos * fix bug * rename some functions * fix routes checkrelease/v1.7
committed by
GitHub
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
80 changed files with 1351 additions and 765 deletions
-
14cmd/serv.go
-
2integrations/api_repo_test.go
-
16models/access.go
-
41models/access_test.go
-
12models/branches.go
-
114models/fixtures/repo_unit.yml
-
2models/fixtures/repository.yml
-
27models/fixtures/team.yml
-
18models/fixtures/team_repo.yml
-
15models/fixtures/team_unit.yml
-
18models/fixtures/team_user.yml
-
4models/fixtures/user.yml
-
32models/issue.go
-
7models/issue_assignees.go
-
6models/issue_comment.go
-
2models/issue_milestone.go
-
5models/lfs_lock.go
-
6models/org_team.go
-
2models/org_team_test.go
-
3models/org_test.go
-
4models/pull.go
-
13models/release.go
-
95models/repo.go
-
270models/repo_permission.go
-
246models/repo_permission_test.go
-
5models/repo_unit.go
-
6models/ssh_key.go
-
29models/user.go
-
2models/user_test.go
-
64modules/context/permission.go
-
123modules/context/repo.go
-
15modules/lfs/server.go
-
24modules/private/internal.go
-
3modules/test/context_tests.go
-
142routers/api/v1/api.go
-
6routers/api/v1/org/team.go
-
18routers/api/v1/repo/collaborators.go
-
6routers/api/v1/repo/file.go
-
3routers/api/v1/repo/fork.go
-
15routers/api/v1/repo/issue.go
-
41routers/api/v1/repo/issue_label.go
-
16routers/api/v1/repo/label.go
-
15routers/api/v1/repo/pull.go
-
12routers/api/v1/repo/release.go
-
20routers/api/v1/repo/repo.go
-
7routers/api/v1/user/repo.go
-
10routers/api/v1/user/star.go
-
10routers/api/v1/user/watch.go
-
36routers/private/internal.go
-
6routers/repo/activity.go
-
5routers/repo/branch.go
-
37routers/repo/http.go
-
187routers/repo/issue.go
-
25routers/repo/issue_watch.go
-
24routers/repo/pull.go
-
6routers/repo/release.go
-
17routers/repo/setting_protected_branch.go
-
10routers/repo/view.go
-
13routers/repo/wiki.go
-
79routers/routes/routes.go
-
7routers/user/home.go
-
10templates/repo/activity.tmpl
-
2templates/repo/bare.tmpl
-
2templates/repo/diff/comments.tmpl
-
20templates/repo/header.tmpl
-
4templates/repo/home.tmpl
-
8templates/repo/issue/labels.tmpl
-
2templates/repo/issue/milestone_new.tmpl
-
6templates/repo/issue/milestones.tmpl
-
4templates/repo/issue/view_content.tmpl
-
2templates/repo/issue/view_content/comments.tmpl
-
10templates/repo/issue/view_content/sidebar.tmpl
-
2templates/repo/issue/view_title.tmpl
-
8templates/repo/release/list.tmpl
-
6templates/repo/settings/options.tmpl
-
2templates/repo/settings/webhook/history.tmpl
-
4templates/repo/sub_menu.tmpl
-
2templates/repo/wiki/pages.tmpl
-
2templates/repo/wiki/start.tmpl
-
2templates/repo/wiki/view.tmpl
@ -0,0 +1,270 @@ |
|||
// Copyright 2018 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 models |
|||
|
|||
// Permission contains all the permissions related variables to a repository for a user
|
|||
type Permission struct { |
|||
AccessMode AccessMode |
|||
Units []*RepoUnit |
|||
UnitsMode map[UnitType]AccessMode |
|||
} |
|||
|
|||
// IsOwner returns true if current user is the owner of repository.
|
|||
func (p *Permission) IsOwner() bool { |
|||
return p.AccessMode >= AccessModeOwner |
|||
} |
|||
|
|||
// IsAdmin returns true if current user has admin or higher access of repository.
|
|||
func (p *Permission) IsAdmin() bool { |
|||
return p.AccessMode >= AccessModeAdmin |
|||
} |
|||
|
|||
// HasAccess returns true if the current user has at least read access to any unit of this repository
|
|||
func (p *Permission) HasAccess() bool { |
|||
if p.UnitsMode == nil { |
|||
return p.AccessMode >= AccessModeRead |
|||
} |
|||
return len(p.UnitsMode) > 0 |
|||
} |
|||
|
|||
// UnitAccessMode returns current user accessmode to the specify unit of the repository
|
|||
func (p *Permission) UnitAccessMode(unitType UnitType) AccessMode { |
|||
if p.UnitsMode == nil { |
|||
for _, u := range p.Units { |
|||
if u.Type == unitType { |
|||
return p.AccessMode |
|||
} |
|||
} |
|||
return AccessModeNone |
|||
} |
|||
return p.UnitsMode[unitType] |
|||
} |
|||
|
|||
// CanAccess returns true if user has mode access to the unit of the repository
|
|||
func (p *Permission) CanAccess(mode AccessMode, unitType UnitType) bool { |
|||
return p.UnitAccessMode(unitType) >= mode |
|||
} |
|||
|
|||
// CanAccessAny returns true if user has mode access to any of the units of the repository
|
|||
func (p *Permission) CanAccessAny(mode AccessMode, unitTypes ...UnitType) bool { |
|||
for _, u := range unitTypes { |
|||
if p.CanAccess(mode, u) { |
|||
return true |
|||
} |
|||
} |
|||
return false |
|||
} |
|||
|
|||
// CanRead returns true if user could read to this unit
|
|||
func (p *Permission) CanRead(unitType UnitType) bool { |
|||
return p.CanAccess(AccessModeRead, unitType) |
|||
} |
|||
|
|||
// CanReadAny returns true if user has read access to any of the units of the repository
|
|||
func (p *Permission) CanReadAny(unitTypes ...UnitType) bool { |
|||
return p.CanAccessAny(AccessModeRead, unitTypes...) |
|||
} |
|||
|
|||
// CanReadIssuesOrPulls returns true if isPull is true and user could read pull requests and
|
|||
// returns true if isPull is false and user could read to issues
|
|||
func (p *Permission) CanReadIssuesOrPulls(isPull bool) bool { |
|||
if isPull { |
|||
return p.CanRead(UnitTypePullRequests) |
|||
} |
|||
return p.CanRead(UnitTypeIssues) |
|||
} |
|||
|
|||
// CanWrite returns true if user could write to this unit
|
|||
func (p *Permission) CanWrite(unitType UnitType) bool { |
|||
return p.CanAccess(AccessModeWrite, unitType) |
|||
} |
|||
|
|||
// CanWriteIssuesOrPulls returns true if isPull is true and user could write to pull requests and
|
|||
// returns true if isPull is false and user could write to issues
|
|||
func (p *Permission) CanWriteIssuesOrPulls(isPull bool) bool { |
|||
if isPull { |
|||
return p.CanWrite(UnitTypePullRequests) |
|||
} |
|||
return p.CanWrite(UnitTypeIssues) |
|||
} |
|||
|
|||
// GetUserRepoPermission returns the user permissions to the repository
|
|||
func GetUserRepoPermission(repo *Repository, user *User) (Permission, error) { |
|||
return getUserRepoPermission(x, repo, user) |
|||
} |
|||
|
|||
func getUserRepoPermission(e Engine, repo *Repository, user *User) (perm Permission, err error) { |
|||
// anonymous user visit private repo.
|
|||
// TODO: anonymous user visit public unit of private repo???
|
|||
if user == nil && repo.IsPrivate { |
|||
perm.AccessMode = AccessModeNone |
|||
return |
|||
} |
|||
|
|||
if err = repo.getUnits(e); err != nil { |
|||
return |
|||
} |
|||
|
|||
perm.Units = repo.Units |
|||
|
|||
// anonymous visit public repo
|
|||
if user == nil { |
|||
perm.AccessMode = AccessModeRead |
|||
return |
|||
} |
|||
|
|||
// Admin or the owner has super access to the repository
|
|||
if user.IsAdmin || user.ID == repo.OwnerID { |
|||
perm.AccessMode = AccessModeOwner |
|||
return |
|||
} |
|||
|
|||
// plain user
|
|||
perm.AccessMode, err = accessLevel(e, user.ID, repo) |
|||
if err != nil { |
|||
return |
|||
} |
|||
|
|||
if err = repo.getOwner(e); err != nil { |
|||
return |
|||
} |
|||
if !repo.Owner.IsOrganization() { |
|||
return |
|||
} |
|||
|
|||
perm.UnitsMode = make(map[UnitType]AccessMode) |
|||
|
|||
// Collaborators on organization
|
|||
if isCollaborator, err := repo.isCollaborator(e, user.ID); err != nil { |
|||
return perm, err |
|||
} else if isCollaborator { |
|||
for _, u := range repo.Units { |
|||
perm.UnitsMode[u.Type] = perm.AccessMode |
|||
} |
|||
} |
|||
|
|||
// get units mode from teams
|
|||
teams, err := getUserRepoTeams(e, repo.OwnerID, user.ID, repo.ID) |
|||
if err != nil { |
|||
return |
|||
} |
|||
|
|||
for _, u := range repo.Units { |
|||
var found bool |
|||
for _, team := range teams { |
|||
if team.unitEnabled(e, u.Type) { |
|||
m := perm.UnitsMode[u.Type] |
|||
if m < team.Authorize { |
|||
perm.UnitsMode[u.Type] = team.Authorize |
|||
} |
|||
found = true |
|||
} |
|||
} |
|||
|
|||
// for a public repo on an organization, user have read permission on non-team defined units.
|
|||
if !found && !repo.IsPrivate { |
|||
if _, ok := perm.UnitsMode[u.Type]; !ok { |
|||
perm.UnitsMode[u.Type] = AccessModeRead |
|||
} |
|||
} |
|||
} |
|||
|
|||
// remove no permission units
|
|||
perm.Units = make([]*RepoUnit, 0, len(repo.Units)) |
|||
for t := range perm.UnitsMode { |
|||
for _, u := range repo.Units { |
|||
if u.Type == t { |
|||
perm.Units = append(perm.Units, u) |
|||
} |
|||
} |
|||
} |
|||
|
|||
return |
|||
} |
|||
|
|||
// IsUserRepoAdmin return ture if user has admin right of a repo
|
|||
func IsUserRepoAdmin(repo *Repository, user *User) (bool, error) { |
|||
return isUserRepoAdmin(x, repo, user) |
|||
} |
|||
|
|||
func isUserRepoAdmin(e Engine, repo *Repository, user *User) (bool, error) { |
|||
if user == nil || repo == nil { |
|||
return false, nil |
|||
} |
|||
if user.IsAdmin { |
|||
return true, nil |
|||
} |
|||
|
|||
mode, err := accessLevel(e, user.ID, repo) |
|||
if err != nil { |
|||
return false, err |
|||
} |
|||
if mode >= AccessModeAdmin { |
|||
return true, nil |
|||
} |
|||
|
|||
teams, err := getUserRepoTeams(e, repo.OwnerID, user.ID, repo.ID) |
|||
if err != nil { |
|||
return false, err |
|||
} |
|||
|
|||
for _, team := range teams { |
|||
if team.Authorize >= AccessModeAdmin { |
|||
return true, nil |
|||
} |
|||
} |
|||
return false, nil |
|||
} |
|||
|
|||
// AccessLevel returns the Access a user has to a repository. Will return NoneAccess if the
|
|||
// user does not have access.
|
|||
func AccessLevel(user *User, repo *Repository) (AccessMode, error) { |
|||
return accessLevelUnit(x, user, repo, UnitTypeCode) |
|||
} |
|||
|
|||
func accessLevelUnit(e Engine, user *User, repo *Repository, unitType UnitType) (AccessMode, error) { |
|||
perm, err := getUserRepoPermission(e, repo, user) |
|||
if err != nil { |
|||
return AccessModeNone, err |
|||
} |
|||
return perm.UnitAccessMode(UnitTypeCode), nil |
|||
} |
|||
|
|||
func hasAccessUnit(e Engine, user *User, repo *Repository, unitType UnitType, testMode AccessMode) (bool, error) { |
|||
mode, err := accessLevelUnit(e, user, repo, unitType) |
|||
return testMode <= mode, err |
|||
} |
|||
|
|||
// HasAccessUnit returns ture if user has testMode to the unit of the repository
|
|||
func HasAccessUnit(user *User, repo *Repository, unitType UnitType, testMode AccessMode) (bool, error) { |
|||
return hasAccessUnit(x, user, repo, unitType, testMode) |
|||
} |
|||
|
|||
// canBeAssigned return true if user could be assigned to a repo
|
|||
// FIXME: user could send PullRequest also could be assigned???
|
|||
func canBeAssigned(e Engine, user *User, repo *Repository) (bool, error) { |
|||
return hasAccessUnit(e, user, repo, UnitTypeCode, AccessModeWrite) |
|||
} |
|||
|
|||
func hasAccess(e Engine, userID int64, repo *Repository) (bool, error) { |
|||
var user *User |
|||
var err error |
|||
if userID > 0 { |
|||
user, err = getUserByID(e, userID) |
|||
if err != nil { |
|||
return false, err |
|||
} |
|||
} |
|||
perm, err := getUserRepoPermission(e, repo, user) |
|||
if err != nil { |
|||
return false, err |
|||
} |
|||
return perm.HasAccess(), nil |
|||
} |
|||
|
|||
// HasAccess returns true if user has access to repo
|
|||
func HasAccess(userID int64, repo *Repository) (bool, error) { |
|||
return hasAccess(x, userID, repo) |
|||
} |
@ -0,0 +1,246 @@ |
|||
// Copyright 2018 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 models |
|||
|
|||
import ( |
|||
"testing" |
|||
|
|||
"github.com/stretchr/testify/assert" |
|||
) |
|||
|
|||
func TestRepoPermissionPublicNonOrgRepo(t *testing.T) { |
|||
assert.NoError(t, PrepareTestDatabase()) |
|||
|
|||
// public non-organization repo
|
|||
repo := AssertExistsAndLoadBean(t, &Repository{ID: 4}).(*Repository) |
|||
assert.NoError(t, repo.getUnits(x)) |
|||
|
|||
// plain user
|
|||
user := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) |
|||
perm, err := GetUserRepoPermission(repo, user) |
|||
assert.NoError(t, err) |
|||
for _, unit := range repo.Units { |
|||
assert.True(t, perm.CanRead(unit.Type)) |
|||
assert.False(t, perm.CanWrite(unit.Type)) |
|||
} |
|||
|
|||
// change to collaborator
|
|||
assert.NoError(t, repo.AddCollaborator(user)) |
|||
perm, err = GetUserRepoPermission(repo, user) |
|||
assert.NoError(t, err) |
|||
for _, unit := range repo.Units { |
|||
assert.True(t, perm.CanRead(unit.Type)) |
|||
assert.True(t, perm.CanWrite(unit.Type)) |
|||
} |
|||
|
|||
// collaborator
|
|||
collaborator := AssertExistsAndLoadBean(t, &User{ID: 4}).(*User) |
|||
perm, err = GetUserRepoPermission(repo, collaborator) |
|||
assert.NoError(t, err) |
|||
for _, unit := range repo.Units { |
|||
assert.True(t, perm.CanRead(unit.Type)) |
|||
assert.True(t, perm.CanWrite(unit.Type)) |
|||
} |
|||
|
|||
// owner
|
|||
owner := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User) |
|||
perm, err = GetUserRepoPermission(repo, owner) |
|||
assert.NoError(t, err) |
|||
for _, unit := range repo.Units { |
|||
assert.True(t, perm.CanRead(unit.Type)) |
|||
assert.True(t, perm.CanWrite(unit.Type)) |
|||
} |
|||
|
|||
// admin
|
|||
admin := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) |
|||
perm, err = GetUserRepoPermission(repo, admin) |
|||
assert.NoError(t, err) |
|||
for _, unit := range repo.Units { |
|||
assert.True(t, perm.CanRead(unit.Type)) |
|||
assert.True(t, perm.CanWrite(unit.Type)) |
|||
} |
|||
} |
|||
|
|||
func TestRepoPermissionPrivateNonOrgRepo(t *testing.T) { |
|||
assert.NoError(t, PrepareTestDatabase()) |
|||
|
|||
// private non-organization repo
|
|||
repo := AssertExistsAndLoadBean(t, &Repository{ID: 2}).(*Repository) |
|||
assert.NoError(t, repo.getUnits(x)) |
|||
|
|||
// plain user
|
|||
user := AssertExistsAndLoadBean(t, &User{ID: 4}).(*User) |
|||
perm, err := GetUserRepoPermission(repo, user) |
|||
assert.NoError(t, err) |
|||
for _, unit := range repo.Units { |
|||
assert.False(t, perm.CanRead(unit.Type)) |
|||
assert.False(t, perm.CanWrite(unit.Type)) |
|||
} |
|||
|
|||
// change to collaborator to default write access
|
|||
assert.NoError(t, repo.AddCollaborator(user)) |
|||
perm, err = GetUserRepoPermission(repo, user) |
|||
assert.NoError(t, err) |
|||
for _, unit := range repo.Units { |
|||
assert.True(t, perm.CanRead(unit.Type)) |
|||
assert.True(t, perm.CanWrite(unit.Type)) |
|||
} |
|||
|
|||
assert.NoError(t, repo.ChangeCollaborationAccessMode(user.ID, AccessModeRead)) |
|||
perm, err = GetUserRepoPermission(repo, user) |
|||
assert.NoError(t, err) |
|||
for _, unit := range repo.Units { |
|||
assert.True(t, perm.CanRead(unit.Type)) |
|||
assert.False(t, perm.CanWrite(unit.Type)) |
|||
} |
|||
|
|||
// owner
|
|||
owner := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) |
|||
perm, err = GetUserRepoPermission(repo, owner) |
|||
assert.NoError(t, err) |
|||
for _, unit := range repo.Units { |
|||
assert.True(t, perm.CanRead(unit.Type)) |
|||
assert.True(t, perm.CanWrite(unit.Type)) |
|||
} |
|||
|
|||
// admin
|
|||
admin := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) |
|||
perm, err = GetUserRepoPermission(repo, admin) |
|||
assert.NoError(t, err) |
|||
for _, unit := range repo.Units { |
|||
assert.True(t, perm.CanRead(unit.Type)) |
|||
assert.True(t, perm.CanWrite(unit.Type)) |
|||
} |
|||
} |
|||
|
|||
func TestRepoPermissionPublicOrgRepo(t *testing.T) { |
|||
assert.NoError(t, PrepareTestDatabase()) |
|||
|
|||
// public organization repo
|
|||
repo := AssertExistsAndLoadBean(t, &Repository{ID: 32}).(*Repository) |
|||
assert.NoError(t, repo.getUnits(x)) |
|||
|
|||
// plain user
|
|||
user := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User) |
|||
perm, err := GetUserRepoPermission(repo, user) |
|||
assert.NoError(t, err) |
|||
for _, unit := range repo.Units { |
|||
assert.True(t, perm.CanRead(unit.Type)) |
|||
assert.False(t, perm.CanWrite(unit.Type)) |
|||
} |
|||
|
|||
// change to collaborator to default write access
|
|||
assert.NoError(t, repo.AddCollaborator(user)) |
|||
perm, err = GetUserRepoPermission(repo, user) |
|||
assert.NoError(t, err) |
|||
for _, unit := range repo.Units { |
|||
assert.True(t, perm.CanRead(unit.Type)) |
|||
assert.True(t, perm.CanWrite(unit.Type)) |
|||
} |
|||
|
|||
assert.NoError(t, repo.ChangeCollaborationAccessMode(user.ID, AccessModeRead)) |
|||
perm, err = GetUserRepoPermission(repo, user) |
|||
assert.NoError(t, err) |
|||
for _, unit := range repo.Units { |
|||
assert.True(t, perm.CanRead(unit.Type)) |
|||
assert.False(t, perm.CanWrite(unit.Type)) |
|||
} |
|||
|
|||
// org member team owner
|
|||
owner := AssertExistsAndLoadBean(t, &User{ID: 2}).(*User) |
|||
perm, err = GetUserRepoPermission(repo, owner) |
|||
assert.NoError(t, err) |
|||
for _, unit := range repo.Units { |
|||
assert.True(t, perm.CanRead(unit.Type)) |
|||
assert.True(t, perm.CanWrite(unit.Type)) |
|||
} |
|||
|
|||
// org member team tester
|
|||
member := AssertExistsAndLoadBean(t, &User{ID: 15}).(*User) |
|||
perm, err = GetUserRepoPermission(repo, member) |
|||
assert.NoError(t, err) |
|||
for _, unit := range repo.Units { |
|||
assert.True(t, perm.CanRead(unit.Type)) |
|||
} |
|||
assert.True(t, perm.CanWrite(UnitTypeIssues)) |
|||
assert.False(t, perm.CanWrite(UnitTypeCode)) |
|||
|
|||
// admin
|
|||
admin := AssertExistsAndLoadBean(t, &User{ID: 1}).(*User) |
|||
perm, err = GetUserRepoPermission(repo, admin) |
|||
assert.NoError(t, err) |
|||
for _, unit := range repo.Units { |
|||
assert.True(t, perm.CanRead(unit.Type)) |
|||
assert.True(t, perm.CanWrite(unit.Type)) |
|||
} |
|||
} |
|||
|
|||
func TestRepoPermissionPrivateOrgRepo(t *testing.T) { |
|||
assert.NoError(t, PrepareTestDatabase()) |
|||
|
|||
// private organization repo
|
|||
repo := AssertExistsAndLoadBean(t, &Repository{ID: 24}).(*Repository) |
|||
assert.NoError(t, repo.getUnits(x)) |
|||
|
|||
// plain user
|
|||
user := AssertExistsAndLoadBean(t, &User{ID: 5}).(*User) |
|||
perm, err := GetUserRepoPermission(repo, user) |
|||
assert.NoError(t, err) |
|||
for _, unit := range repo.Units { |
|||
assert.False(t, perm.CanRead(unit.Type)) |
|||
assert.False(t, perm.CanWrite(unit.Type)) |
|||
} |
|||
|
|||
// change to collaborator to default write access
|
|||
assert.NoError(t, repo.AddCollaborator(user)) |
|||
perm, err = GetUserRepoPermission(repo, user) |
|||
assert.NoError(t, err) |
|||
for _, unit := range repo.Units { |
|||
assert.True(t, perm.CanRead(unit.Type)) |
|||
assert.True(t, perm.CanWrite(unit.Type)) |
|||
} |
|||
|
|||
assert.NoError(t, repo.ChangeCollaborationAccessMode(user.ID, AccessModeRead)) |
|||
perm, err = GetUserRepoPermission(repo, user) |
|||
assert.NoError(t, err) |
|||
for _, unit := range repo.Units { |
|||
assert.True(t, perm.CanRead(unit.Type)) |
|||