Clarify permission "HasAccess" behavior (#30585)
Follow #30495 "HasAccess" behavior wasn't clear, to make it clear: * Use a new name `HasAnyUnitAccess`, it will be easier to review related code and permission problems. * Separate everyone access mode to a separate field, then all calls to HasAccess are reverted to old behavior before #30495. * Add new tests. --------- Co-authored-by: Giteabot <teabot@gitea.io>
This commit is contained in:
parent
89e39872ff
commit
48d4580dd5
@ -118,7 +118,7 @@ func removeAllRepositories(ctx context.Context, t *organization.Team) (err error
|
|||||||
|
|
||||||
// Remove watches from all users and now unaccessible repos
|
// Remove watches from all users and now unaccessible repos
|
||||||
for _, user := range t.Members {
|
for _, user := range t.Members {
|
||||||
has, err := access_model.HasAccess(ctx, user.ID, repo)
|
has, err := access_model.HasAnyUnitAccess(ctx, user.ID, repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
} else if has {
|
} else if has {
|
||||||
@ -544,7 +544,7 @@ func ReconsiderRepoIssuesAssignee(ctx context.Context, repo *repo_model.Reposito
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ReconsiderWatches(ctx context.Context, repo *repo_model.Repository, user *user_model.User) error {
|
func ReconsiderWatches(ctx context.Context, repo *repo_model.Repository, user *user_model.User) error {
|
||||||
if has, err := access_model.HasAccess(ctx, user.ID, repo); err != nil || has {
|
if has, err := access_model.HasAnyUnitAccess(ctx, user.ID, repo); err != nil || has {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := repo_model.WatchRepo(ctx, user, repo, false); err != nil {
|
if err := repo_model.WatchRepo(ctx, user, repo, false); err != nil {
|
||||||
|
@ -79,17 +79,17 @@ func TestHasAccess(t *testing.T) {
|
|||||||
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})
|
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})
|
||||||
assert.True(t, repo2.IsPrivate)
|
assert.True(t, repo2.IsPrivate)
|
||||||
|
|
||||||
has, err := access_model.HasAccess(db.DefaultContext, user1.ID, repo1)
|
has, err := access_model.HasAnyUnitAccess(db.DefaultContext, user1.ID, repo1)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.True(t, has)
|
assert.True(t, has)
|
||||||
|
|
||||||
_, err = access_model.HasAccess(db.DefaultContext, user1.ID, repo2)
|
_, err = access_model.HasAnyUnitAccess(db.DefaultContext, user1.ID, repo2)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
_, err = access_model.HasAccess(db.DefaultContext, user2.ID, repo1)
|
_, err = access_model.HasAnyUnitAccess(db.DefaultContext, user2.ID, repo1)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
_, err = access_model.HasAccess(db.DefaultContext, user2.ID, repo2)
|
_, err = access_model.HasAnyUnitAccess(db.DefaultContext, user2.ID, repo2)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,6 +24,8 @@ type Permission struct {
|
|||||||
|
|
||||||
units []*repo_model.RepoUnit
|
units []*repo_model.RepoUnit
|
||||||
unitsMode map[unit.Type]perm_model.AccessMode
|
unitsMode map[unit.Type]perm_model.AccessMode
|
||||||
|
|
||||||
|
everyoneAccessMode map[unit.Type]perm_model.AccessMode
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsOwner returns true if current user is the owner of repository.
|
// IsOwner returns true if current user is the owner of repository.
|
||||||
@ -36,9 +38,24 @@ func (p *Permission) IsAdmin() bool {
|
|||||||
return p.AccessMode >= perm_model.AccessModeAdmin
|
return p.AccessMode >= perm_model.AccessModeAdmin
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasAccess returns true if the current user might have at least read access to any unit of this repository
|
// HasAnyUnitAccess returns true if the user might have at least one access mode to any unit of this repository.
|
||||||
func (p *Permission) HasAccess() bool {
|
// It doesn't count the "everyone access mode".
|
||||||
return len(p.unitsMode) > 0 || p.AccessMode >= perm_model.AccessModeRead
|
func (p *Permission) HasAnyUnitAccess() bool {
|
||||||
|
for _, v := range p.unitsMode {
|
||||||
|
if v >= perm_model.AccessModeRead {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p.AccessMode >= perm_model.AccessModeRead
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Permission) HasAnyUnitAccessOrEveryoneAccess() bool {
|
||||||
|
for _, v := range p.everyoneAccessMode {
|
||||||
|
if v >= perm_model.AccessModeRead {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p.HasAnyUnitAccess()
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasUnits returns true if the permission contains attached units
|
// HasUnits returns true if the permission contains attached units
|
||||||
@ -56,16 +73,16 @@ func (p *Permission) GetFirstUnitRepoID() int64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UnitAccessMode returns current user access mode to the specify unit of the repository
|
// UnitAccessMode returns current user access mode to the specify unit of the repository
|
||||||
|
// It also considers "everyone access mode"
|
||||||
func (p *Permission) UnitAccessMode(unitType unit.Type) perm_model.AccessMode {
|
func (p *Permission) UnitAccessMode(unitType unit.Type) perm_model.AccessMode {
|
||||||
if p.unitsMode != nil {
|
// if the units map contains the access mode, use it, but admin/owner mode could override it
|
||||||
// if the units map contains the access mode, use it, but admin/owner mode could override it
|
if m, ok := p.unitsMode[unitType]; ok {
|
||||||
if m, ok := p.unitsMode[unitType]; ok {
|
return util.Iif(p.AccessMode >= perm_model.AccessModeAdmin, p.AccessMode, m)
|
||||||
return util.Iif(p.AccessMode >= perm_model.AccessModeAdmin, p.AccessMode, m)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// if the units map does not contain the access mode, return the default access mode if the unit exists
|
// if the units map does not contain the access mode, return the default access mode if the unit exists
|
||||||
|
unitDefaultAccessMode := max(p.AccessMode, p.everyoneAccessMode[unitType])
|
||||||
hasUnit := slices.ContainsFunc(p.units, func(u *repo_model.RepoUnit) bool { return u.Type == unitType })
|
hasUnit := slices.ContainsFunc(p.units, func(u *repo_model.RepoUnit) bool { return u.Type == unitType })
|
||||||
return util.Iif(hasUnit, p.AccessMode, perm_model.AccessModeNone)
|
return util.Iif(hasUnit, unitDefaultAccessMode, perm_model.AccessModeNone)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Permission) SetUnitsWithDefaultAccessMode(units []*repo_model.RepoUnit, mode perm_model.AccessMode) {
|
func (p *Permission) SetUnitsWithDefaultAccessMode(units []*repo_model.RepoUnit, mode perm_model.AccessMode) {
|
||||||
@ -159,14 +176,15 @@ func (p *Permission) LogString() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func applyEveryoneRepoPermission(user *user_model.User, perm *Permission) {
|
func applyEveryoneRepoPermission(user *user_model.User, perm *Permission) {
|
||||||
if user != nil && user.ID > 0 {
|
if user == nil || user.ID <= 0 {
|
||||||
for _, u := range perm.units {
|
return
|
||||||
if perm.unitsMode == nil {
|
}
|
||||||
perm.unitsMode = make(map[unit.Type]perm_model.AccessMode)
|
for _, u := range perm.units {
|
||||||
}
|
if u.EveryoneAccessMode >= perm_model.AccessModeRead && u.EveryoneAccessMode > perm.everyoneAccessMode[u.Type] {
|
||||||
if u.EveryoneAccessMode >= perm_model.AccessModeRead && u.EveryoneAccessMode > perm.unitsMode[u.Type] {
|
if perm.everyoneAccessMode == nil {
|
||||||
perm.unitsMode[u.Type] = u.EveryoneAccessMode
|
perm.everyoneAccessMode = make(map[unit.Type]perm_model.AccessMode)
|
||||||
}
|
}
|
||||||
|
perm.everyoneAccessMode[u.Type] = u.EveryoneAccessMode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -373,8 +391,8 @@ func CanBeAssigned(ctx context.Context, user *user_model.User, repo *repo_model.
|
|||||||
perm.CanAccessAny(perm_model.AccessModeRead, unit.TypePullRequests), nil
|
perm.CanAccessAny(perm_model.AccessModeRead, unit.TypePullRequests), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasAccess returns true if user has access to repo
|
// HasAnyUnitAccess see the comment of "perm.HasAnyUnitAccess"
|
||||||
func HasAccess(ctx context.Context, userID int64, repo *repo_model.Repository) (bool, error) {
|
func HasAnyUnitAccess(ctx context.Context, userID int64, repo *repo_model.Repository) (bool, error) {
|
||||||
var user *user_model.User
|
var user *user_model.User
|
||||||
var err error
|
var err error
|
||||||
if userID > 0 {
|
if userID > 0 {
|
||||||
@ -387,7 +405,7 @@ func HasAccess(ctx context.Context, userID int64, repo *repo_model.Repository) (
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
return perm.HasAccess(), nil
|
return perm.HasAnyUnitAccess(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getUsersWithAccessMode returns users that have at least given access mode to the repository.
|
// getUsersWithAccessMode returns users that have at least given access mode to the repository.
|
||||||
|
@ -14,16 +14,54 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestHasAnyUnitAccess(t *testing.T) {
|
||||||
|
perm := Permission{}
|
||||||
|
assert.False(t, perm.HasAnyUnitAccess())
|
||||||
|
|
||||||
|
perm = Permission{
|
||||||
|
units: []*repo_model.RepoUnit{{Type: unit.TypeWiki}},
|
||||||
|
}
|
||||||
|
assert.False(t, perm.HasAnyUnitAccess())
|
||||||
|
assert.False(t, perm.HasAnyUnitAccessOrEveryoneAccess())
|
||||||
|
|
||||||
|
perm = Permission{
|
||||||
|
units: []*repo_model.RepoUnit{{Type: unit.TypeWiki}},
|
||||||
|
everyoneAccessMode: map[unit.Type]perm_model.AccessMode{unit.TypeIssues: perm_model.AccessModeRead},
|
||||||
|
}
|
||||||
|
assert.False(t, perm.HasAnyUnitAccess())
|
||||||
|
assert.True(t, perm.HasAnyUnitAccessOrEveryoneAccess())
|
||||||
|
|
||||||
|
perm = Permission{
|
||||||
|
AccessMode: perm_model.AccessModeRead,
|
||||||
|
units: []*repo_model.RepoUnit{{Type: unit.TypeWiki}},
|
||||||
|
}
|
||||||
|
assert.True(t, perm.HasAnyUnitAccess())
|
||||||
|
|
||||||
|
perm = Permission{
|
||||||
|
unitsMode: map[unit.Type]perm_model.AccessMode{unit.TypeWiki: perm_model.AccessModeRead},
|
||||||
|
}
|
||||||
|
assert.True(t, perm.HasAnyUnitAccess())
|
||||||
|
}
|
||||||
|
|
||||||
func TestApplyEveryoneRepoPermission(t *testing.T) {
|
func TestApplyEveryoneRepoPermission(t *testing.T) {
|
||||||
perm := Permission{
|
perm := Permission{
|
||||||
AccessMode: perm_model.AccessModeNone,
|
AccessMode: perm_model.AccessModeNone,
|
||||||
units: []*repo_model.RepoUnit{
|
units: []*repo_model.RepoUnit{
|
||||||
{Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeNone},
|
{Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
applyEveryoneRepoPermission(nil, &perm)
|
applyEveryoneRepoPermission(nil, &perm)
|
||||||
assert.False(t, perm.CanRead(unit.TypeWiki))
|
assert.False(t, perm.CanRead(unit.TypeWiki))
|
||||||
|
|
||||||
|
perm = Permission{
|
||||||
|
AccessMode: perm_model.AccessModeNone,
|
||||||
|
units: []*repo_model.RepoUnit{
|
||||||
|
{Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
applyEveryoneRepoPermission(&user_model.User{ID: 0}, &perm)
|
||||||
|
assert.False(t, perm.CanRead(unit.TypeWiki))
|
||||||
|
|
||||||
perm = Permission{
|
perm = Permission{
|
||||||
AccessMode: perm_model.AccessModeNone,
|
AccessMode: perm_model.AccessModeNone,
|
||||||
units: []*repo_model.RepoUnit{
|
units: []*repo_model.RepoUnit{
|
||||||
@ -40,8 +78,8 @@ func TestApplyEveryoneRepoPermission(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
applyEveryoneRepoPermission(&user_model.User{ID: 1}, &perm)
|
applyEveryoneRepoPermission(&user_model.User{ID: 1}, &perm)
|
||||||
assert.True(t, perm.CanRead(unit.TypeWiki))
|
// it should work the same as "EveryoneAccessMode: none" because the default AccessMode should be applied to units
|
||||||
assert.False(t, perm.CanWrite(unit.TypeWiki)) // because there is no unit mode, so the everyone-mode is used as the unit's access mode
|
assert.True(t, perm.CanWrite(unit.TypeWiki))
|
||||||
|
|
||||||
perm = Permission{
|
perm = Permission{
|
||||||
units: []*repo_model.RepoUnit{
|
units: []*repo_model.RepoUnit{
|
||||||
|
@ -218,7 +218,7 @@ func repoAssignment() func(ctx *context.APIContext) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ctx.Repo.HasAccess() {
|
if !ctx.Repo.Permission.HasAnyUnitAccess() {
|
||||||
ctx.NotFound()
|
ctx.NotFound()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -412,7 +412,7 @@ func reqRepoReader(unitType unit.Type) func(ctx *context.APIContext) {
|
|||||||
// reqAnyRepoReader user should have any permission to read repository or permissions of site admin
|
// reqAnyRepoReader user should have any permission to read repository or permissions of site admin
|
||||||
func reqAnyRepoReader() func(ctx *context.APIContext) {
|
func reqAnyRepoReader() func(ctx *context.APIContext) {
|
||||||
return func(ctx *context.APIContext) {
|
return func(ctx *context.APIContext) {
|
||||||
if !ctx.Repo.HasAccess() && !ctx.IsUserSiteAdmin() {
|
if !ctx.Repo.Permission.HasAnyUnitAccess() && !ctx.IsUserSiteAdmin() {
|
||||||
ctx.Error(http.StatusForbidden, "reqAnyRepoReader", "user should have any permission to read repository or permissions of site admin")
|
ctx.Error(http.StatusForbidden, "reqAnyRepoReader", "user should have any permission to read repository or permissions of site admin")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -585,7 +585,7 @@ func GetByID(ctx *context.APIContext) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
|
ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
|
||||||
return
|
return
|
||||||
} else if !permission.HasAccess() {
|
} else if !permission.HasAnyUnitAccess() {
|
||||||
ctx.NotFound()
|
ctx.NotFound()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -82,7 +82,7 @@ func ListPackages(ctx *context.Context) {
|
|||||||
ctx.ServerError("GetUserRepoPermission", err)
|
ctx.ServerError("GetUserRepoPermission", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
repositoryAccessMap[pd.Repository.ID] = permission.HasAccess()
|
repositoryAccessMap[pd.Repository.ID] = permission.HasAnyUnitAccess()
|
||||||
}
|
}
|
||||||
|
|
||||||
hasPackages, err := packages_model.HasOwnerPackages(ctx, ctx.ContextUser.ID)
|
hasPackages, err := packages_model.HasOwnerPackages(ctx, ctx.ContextUser.ID)
|
||||||
@ -276,7 +276,7 @@ func ViewPackageVersion(ctx *context.Context) {
|
|||||||
ctx.ServerError("GetUserRepoPermission", err)
|
ctx.ServerError("GetUserRepoPermission", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
hasRepositoryAccess = permission.HasAccess()
|
hasRepositoryAccess = permission.HasAnyUnitAccess()
|
||||||
}
|
}
|
||||||
ctx.Data["HasRepositoryAccess"] = hasRepositoryAccess
|
ctx.Data["HasRepositoryAccess"] = hasRepositoryAccess
|
||||||
|
|
||||||
|
@ -374,8 +374,7 @@ func repoAssignment(ctx *Context, repo *repo_model.Repository) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check access.
|
if !ctx.Repo.Permission.HasAnyUnitAccessOrEveryoneAccess() {
|
||||||
if !ctx.Repo.Permission.HasAccess() {
|
|
||||||
if ctx.FormString("go-get") == "1" {
|
if ctx.FormString("go-get") == "1" {
|
||||||
EarlyResponseForGoGetMeta(ctx)
|
EarlyResponseForGoGetMeta(ctx)
|
||||||
return
|
return
|
||||||
|
@ -21,7 +21,7 @@ func ToPackage(ctx context.Context, pd *packages.PackageDescriptor, doer *user_m
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if permission.HasAccess() {
|
if permission.HasAnyUnitAccess() {
|
||||||
repo = ToRepo(ctx, pd.Repository, permission)
|
repo = ToRepo(ctx, pd.Repository, permission)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -374,7 +374,7 @@ func removeRepositoryFromTeam(ctx context.Context, t *organization.Team, repo *r
|
|||||||
return fmt.Errorf("GetTeamMembers: %w", err)
|
return fmt.Errorf("GetTeamMembers: %w", err)
|
||||||
}
|
}
|
||||||
for _, member := range teamMembers {
|
for _, member := range teamMembers {
|
||||||
has, err := access_model.HasAccess(ctx, member.ID, repo)
|
has, err := access_model.HasAnyUnitAccess(ctx, member.ID, repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
} else if has {
|
} else if has {
|
||||||
|
@ -387,7 +387,7 @@ func StartRepositoryTransfer(ctx context.Context, doer, newOwner *user_model.Use
|
|||||||
}
|
}
|
||||||
|
|
||||||
// In case the new owner would not have sufficient access to the repo, give access rights for read
|
// In case the new owner would not have sufficient access to the repo, give access rights for read
|
||||||
hasAccess, err := access_model.HasAccess(ctx, newOwner.ID, repo)
|
hasAccess, err := access_model.HasAnyUnitAccess(ctx, newOwner.ID, repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -67,13 +67,13 @@ func TestStartRepositoryTransferSetPermission(t *testing.T) {
|
|||||||
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3})
|
||||||
repo.Owner = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
repo.Owner = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
|
||||||
|
|
||||||
hasAccess, err := access_model.HasAccess(db.DefaultContext, recipient.ID, repo)
|
hasAccess, err := access_model.HasAnyUnitAccess(db.DefaultContext, recipient.ID, repo)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.False(t, hasAccess)
|
assert.False(t, hasAccess)
|
||||||
|
|
||||||
assert.NoError(t, StartRepositoryTransfer(db.DefaultContext, doer, recipient, repo, nil))
|
assert.NoError(t, StartRepositoryTransfer(db.DefaultContext, doer, recipient, repo, nil))
|
||||||
|
|
||||||
hasAccess, err = access_model.HasAccess(db.DefaultContext, recipient.ID, repo)
|
hasAccess, err = access_model.HasAnyUnitAccess(db.DefaultContext, recipient.ID, repo)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.True(t, hasAccess)
|
assert.True(t, hasAccess)
|
||||||
|
|
||||||
|
@ -222,7 +222,7 @@ func TestAPISearchRepo(t *testing.T) {
|
|||||||
assert.Len(t, repoNames, expected.count)
|
assert.Len(t, repoNames, expected.count)
|
||||||
for _, repo := range body.Data {
|
for _, repo := range body.Data {
|
||||||
r := getRepo(t, repo.ID)
|
r := getRepo(t, repo.ID)
|
||||||
hasAccess, err := access_model.HasAccess(db.DefaultContext, userID, r)
|
hasAccess, err := access_model.HasAnyUnitAccess(db.DefaultContext, userID, r)
|
||||||
assert.NoError(t, err, "Error when checking if User: %d has access to %s: %v", userID, repo.FullName, err)
|
assert.NoError(t, err, "Error when checking if User: %d has access to %s: %v", userID, repo.FullName, err)
|
||||||
assert.True(t, hasAccess, "User: %d does not have access to %s", userID, repo.FullName)
|
assert.True(t, hasAccess, "User: %d does not have access to %s", userID, repo.FullName)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user