Move access and repo permission to models/perm/access (#19350)

* Move access and repo permission to models/perm/access

* Remove unnecessary code
This commit is contained in:
Lunny Xiao 2022-05-11 18:09:36 +08:00 committed by GitHub
parent 8e8e936ada
commit cbd45471b1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
72 changed files with 608 additions and 511 deletions

View file

@ -0,0 +1,244 @@
// 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
// license that can be found in the LICENSE file.
package access
import (
"context"
"fmt"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/perm"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
)
// Access represents the highest access level of a user to the repository. The only access type
// that is not in this table is the real owner of a repository. In case of an organization
// repository, the members of the owners team are in this table.
type Access struct {
ID int64 `xorm:"pk autoincr"`
UserID int64 `xorm:"UNIQUE(s)"`
RepoID int64 `xorm:"UNIQUE(s)"`
Mode perm.AccessMode
}
func init() {
db.RegisterModel(new(Access))
}
func accessLevel(e db.Engine, user *user_model.User, repo *repo_model.Repository) (perm.AccessMode, error) {
mode := perm.AccessModeNone
var userID int64
restricted := false
if user != nil {
userID = user.ID
restricted = user.IsRestricted
}
if !restricted && !repo.IsPrivate {
mode = perm.AccessModeRead
}
if userID == 0 {
return mode, nil
}
if userID == repo.OwnerID {
return perm.AccessModeOwner, nil
}
a := &Access{UserID: userID, RepoID: repo.ID}
if has, err := e.Get(a); !has || err != nil {
return mode, err
}
return a.Mode, nil
}
func maxAccessMode(modes ...perm.AccessMode) perm.AccessMode {
max := perm.AccessModeNone
for _, mode := range modes {
if mode > max {
max = mode
}
}
return max
}
type userAccess struct {
User *user_model.User
Mode perm.AccessMode
}
// updateUserAccess updates an access map so that user has at least mode
func updateUserAccess(accessMap map[int64]*userAccess, user *user_model.User, mode perm.AccessMode) {
if ua, ok := accessMap[user.ID]; ok {
ua.Mode = maxAccessMode(ua.Mode, mode)
} else {
accessMap[user.ID] = &userAccess{User: user, Mode: mode}
}
}
// FIXME: do cross-comparison so reduce deletions and additions to the minimum?
func refreshAccesses(e db.Engine, repo *repo_model.Repository, accessMap map[int64]*userAccess) (err error) {
minMode := perm.AccessModeRead
if !repo.IsPrivate {
minMode = perm.AccessModeWrite
}
newAccesses := make([]Access, 0, len(accessMap))
for userID, ua := range accessMap {
if ua.Mode < minMode && !ua.User.IsRestricted {
continue
}
newAccesses = append(newAccesses, Access{
UserID: userID,
RepoID: repo.ID,
Mode: ua.Mode,
})
}
// Delete old accesses and insert new ones for repository.
if _, err = e.Delete(&Access{RepoID: repo.ID}); err != nil {
return fmt.Errorf("delete old accesses: %v", err)
}
if len(newAccesses) == 0 {
return nil
}
if _, err = e.Insert(newAccesses); err != nil {
return fmt.Errorf("insert new accesses: %v", err)
}
return nil
}
// refreshCollaboratorAccesses retrieves repository collaborations with their access modes.
func refreshCollaboratorAccesses(ctx context.Context, repoID int64, accessMap map[int64]*userAccess) error {
collaborators, err := repo_model.GetCollaborators(ctx, repoID, db.ListOptions{})
if err != nil {
return fmt.Errorf("getCollaborations: %v", err)
}
for _, c := range collaborators {
if c.User.IsGhost() {
continue
}
updateUserAccess(accessMap, c.User, c.Collaboration.Mode)
}
return nil
}
// RecalculateTeamAccesses recalculates new accesses for teams of an organization
// except the team whose ID is given. It is used to assign a team ID when
// remove repository from that team.
func RecalculateTeamAccesses(ctx context.Context, repo *repo_model.Repository, ignTeamID int64) (err error) {
accessMap := make(map[int64]*userAccess, 20)
if err = repo.GetOwner(ctx); err != nil {
return err
} else if !repo.Owner.IsOrganization() {
return fmt.Errorf("owner is not an organization: %d", repo.OwnerID)
}
e := db.GetEngine(ctx)
if err = refreshCollaboratorAccesses(ctx, repo.ID, accessMap); err != nil {
return fmt.Errorf("refreshCollaboratorAccesses: %v", err)
}
teams, err := organization.FindOrgTeams(ctx, repo.Owner.ID)
if err != nil {
return err
}
for _, t := range teams {
if t.ID == ignTeamID {
continue
}
// Owner team gets owner access, and skip for teams that do not
// have relations with repository.
if t.IsOwnerTeam() {
t.AccessMode = perm.AccessModeOwner
} else if !organization.HasTeamRepo(ctx, t.OrgID, t.ID, repo.ID) {
continue
}
if err = t.GetMembersCtx(ctx); err != nil {
return fmt.Errorf("getMembers '%d': %v", t.ID, err)
}
for _, m := range t.Members {
updateUserAccess(accessMap, m, t.AccessMode)
}
}
return refreshAccesses(e, repo, accessMap)
}
// RecalculateUserAccess recalculates new access for a single user
// Usable if we know access only affected one user
func RecalculateUserAccess(ctx context.Context, repo *repo_model.Repository, uid int64) (err error) {
minMode := perm.AccessModeRead
if !repo.IsPrivate {
minMode = perm.AccessModeWrite
}
accessMode := perm.AccessModeNone
e := db.GetEngine(ctx)
collaborator, err := repo_model.GetCollaboration(ctx, repo.ID, uid)
if err != nil {
return err
} else if collaborator != nil {
accessMode = collaborator.Mode
}
if err = repo.GetOwner(ctx); err != nil {
return err
} else if repo.Owner.IsOrganization() {
var teams []organization.Team
if err := e.Join("INNER", "team_repo", "team_repo.team_id = team.id").
Join("INNER", "team_user", "team_user.team_id = team.id").
Where("team.org_id = ?", repo.OwnerID).
And("team_repo.repo_id=?", repo.ID).
And("team_user.uid=?", uid).
Find(&teams); err != nil {
return err
}
for _, t := range teams {
if t.IsOwnerTeam() {
t.AccessMode = perm.AccessModeOwner
}
accessMode = maxAccessMode(accessMode, t.AccessMode)
}
}
// Delete old user accesses and insert new one for repository.
if _, err = e.Delete(&Access{RepoID: repo.ID, UserID: uid}); err != nil {
return fmt.Errorf("delete old user accesses: %v", err)
} else if accessMode >= minMode {
if err = db.Insert(ctx, &Access{RepoID: repo.ID, UserID: uid, Mode: accessMode}); err != nil {
return fmt.Errorf("insert new user accesses: %v", err)
}
}
return nil
}
// RecalculateAccesses recalculates all accesses for repository.
func RecalculateAccesses(ctx context.Context, repo *repo_model.Repository) error {
if repo.Owner.IsOrganization() {
return RecalculateTeamAccesses(ctx, repo, 0)
}
e := db.GetEngine(ctx)
accessMap := make(map[int64]*userAccess, 20)
if err := refreshCollaboratorAccesses(ctx, repo.ID, accessMap); err != nil {
return fmt.Errorf("refreshCollaboratorAccesses: %v", err)
}
return refreshAccesses(e, repo, accessMap)
}

View file

@ -0,0 +1,126 @@
// Copyright 2017 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 access
import (
"testing"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/perm"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"github.com/stretchr/testify/assert"
)
func TestAccessLevel(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}).(*user_model.User)
user29 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 29}).(*user_model.User)
// A public repository owned by User 2
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository)
assert.False(t, repo1.IsPrivate)
// A private repository owned by Org 3
repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}).(*repo_model.Repository)
assert.True(t, repo3.IsPrivate)
// Another public repository
repo4 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}).(*repo_model.Repository)
assert.False(t, repo4.IsPrivate)
// org. owned private repo
repo24 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 24}).(*repo_model.Repository)
level, err := AccessLevel(user2, repo1)
assert.NoError(t, err)
assert.Equal(t, perm.AccessModeOwner, level)
level, err = AccessLevel(user2, repo3)
assert.NoError(t, err)
assert.Equal(t, perm.AccessModeOwner, level)
level, err = AccessLevel(user5, repo1)
assert.NoError(t, err)
assert.Equal(t, perm.AccessModeRead, level)
level, err = AccessLevel(user5, repo3)
assert.NoError(t, err)
assert.Equal(t, perm.AccessModeNone, level)
// restricted user has no access to a public repo
level, err = AccessLevel(user29, repo1)
assert.NoError(t, err)
assert.Equal(t, perm.AccessModeNone, level)
// ... unless he's a collaborator
level, err = AccessLevel(user29, repo4)
assert.NoError(t, err)
assert.Equal(t, perm.AccessModeWrite, level)
// ... or a team member
level, err = AccessLevel(user29, repo24)
assert.NoError(t, err)
assert.Equal(t, perm.AccessModeRead, level)
}
func TestHasAccess(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}).(*user_model.User)
// A public repository owned by User 2
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository)
assert.False(t, repo1.IsPrivate)
// A private repository owned by Org 3
repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}).(*repo_model.Repository)
assert.True(t, repo2.IsPrivate)
has, err := HasAccess(db.DefaultContext, user1.ID, repo1)
assert.NoError(t, err)
assert.True(t, has)
_, err = HasAccess(db.DefaultContext, user1.ID, repo2)
assert.NoError(t, err)
_, err = HasAccess(db.DefaultContext, user2.ID, repo1)
assert.NoError(t, err)
_, err = HasAccess(db.DefaultContext, user2.ID, repo2)
assert.NoError(t, err)
}
func TestRepository_RecalculateAccesses(t *testing.T) {
// test with organization repo
assert.NoError(t, unittest.PrepareTestDatabase())
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}).(*repo_model.Repository)
assert.NoError(t, repo1.GetOwner(db.DefaultContext))
_, err := db.GetEngine(db.DefaultContext).Delete(&repo_model.Collaboration{UserID: 2, RepoID: 3})
assert.NoError(t, err)
assert.NoError(t, RecalculateAccesses(db.DefaultContext, repo1))
access := &Access{UserID: 2, RepoID: 3}
has, err := db.GetEngine(db.DefaultContext).Get(access)
assert.NoError(t, err)
assert.True(t, has)
assert.Equal(t, perm.AccessModeOwner, access.Mode)
}
func TestRepository_RecalculateAccesses2(t *testing.T) {
// test with non-organization repo
assert.NoError(t, unittest.PrepareTestDatabase())
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}).(*repo_model.Repository)
assert.NoError(t, repo1.GetOwner(db.DefaultContext))
_, err := db.GetEngine(db.DefaultContext).Delete(&repo_model.Collaboration{UserID: 4, RepoID: 4})
assert.NoError(t, err)
assert.NoError(t, RecalculateAccesses(db.DefaultContext, repo1))
has, err := db.GetEngine(db.DefaultContext).Get(&Access{UserID: 4, RepoID: 4})
assert.NoError(t, err)
assert.False(t, has)
}

View file

@ -0,0 +1,32 @@
// Copyright 2020 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 access
import (
"path/filepath"
"testing"
"code.gitea.io/gitea/models/unittest"
_ "code.gitea.io/gitea/models/repo"
)
func TestMain(m *testing.M) {
unittest.MainTest(m, &unittest.TestOptions{
GiteaRootPath: filepath.Join("..", "..", ".."),
FixtureFiles: []string{
"access.yml",
"user.yml",
"repository.yml",
"collaboration.yml",
"org_user.yml",
"repo_unit.yml",
"team_user.yml",
"team_repo.yml",
"team.yml",
"team_unit.yml",
},
})
}

View file

@ -0,0 +1,440 @@
// 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 access
import (
"context"
"fmt"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization"
perm_model "code.gitea.io/gitea/models/perm"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
)
// Permission contains all the permissions related variables to a repository for a user
type Permission struct {
AccessMode perm_model.AccessMode
Units []*repo_model.RepoUnit
UnitsMode map[unit.Type]perm_model.AccessMode
}
// IsOwner returns true if current user is the owner of repository.
func (p *Permission) IsOwner() bool {
return p.AccessMode >= perm_model.AccessModeOwner
}
// IsAdmin returns true if current user has admin or higher access of repository.
func (p *Permission) IsAdmin() bool {
return p.AccessMode >= perm_model.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 >= perm_model.AccessModeRead
}
return len(p.UnitsMode) > 0
}
// UnitAccessMode returns current user accessmode to the specify unit of the repository
func (p *Permission) UnitAccessMode(unitType unit.Type) perm_model.AccessMode {
if p.UnitsMode == nil {
for _, u := range p.Units {
if u.Type == unitType {
return p.AccessMode
}
}
return perm_model.AccessModeNone
}
return p.UnitsMode[unitType]
}
// CanAccess returns true if user has mode access to the unit of the repository
func (p *Permission) CanAccess(mode perm_model.AccessMode, unitType unit.Type) 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 perm_model.AccessMode, unitTypes ...unit.Type) 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 unit.Type) bool {
return p.CanAccess(perm_model.AccessModeRead, unitType)
}
// CanReadAny returns true if user has read access to any of the units of the repository
func (p *Permission) CanReadAny(unitTypes ...unit.Type) bool {
return p.CanAccessAny(perm_model.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(unit.TypePullRequests)
}
return p.CanRead(unit.TypeIssues)
}
// CanWrite returns true if user could write to this unit
func (p *Permission) CanWrite(unitType unit.Type) bool {
return p.CanAccess(perm_model.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(unit.TypePullRequests)
}
return p.CanWrite(unit.TypeIssues)
}
// ColorFormat writes a colored string for these Permissions
func (p *Permission) ColorFormat(s fmt.State) {
noColor := log.ColorBytes(log.Reset)
format := "perm_model.AccessMode: %-v, %d Units, %d UnitsMode(s): [ "
args := []interface{}{
p.AccessMode,
log.NewColoredValueBytes(len(p.Units), &noColor),
log.NewColoredValueBytes(len(p.UnitsMode), &noColor),
}
if s.Flag('+') {
for i, unit := range p.Units {
config := ""
if unit.Config != nil {
configBytes, err := unit.Config.ToDB()
config = string(configBytes)
if err != nil {
config = err.Error()
}
}
format += "\nUnits[%d]: ID: %d RepoID: %d Type: %-v Config: %s"
args = append(args,
log.NewColoredValueBytes(i, &noColor),
log.NewColoredIDValue(unit.ID),
log.NewColoredIDValue(unit.RepoID),
unit.Type,
config)
}
for key, value := range p.UnitsMode {
format += "\nUnitMode[%-v]: %-v"
args = append(args,
key,
value)
}
} else {
format += "..."
}
format += " ]"
log.ColorFprintf(s, format, args...)
}
// GetUserRepoPermission returns the user permissions to the repository
func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (perm Permission, err error) {
if log.IsTrace() {
defer func() {
if user == nil {
log.Trace("Permission Loaded for anonymous user in %-v:\nPermissions: %-+v",
repo,
perm)
return
}
log.Trace("Permission Loaded for %-v in %-v:\nPermissions: %-+v",
user,
repo,
perm)
}()
}
// anonymous user visit private repo.
// TODO: anonymous user visit public unit of private repo???
if user == nil && repo.IsPrivate {
perm.AccessMode = perm_model.AccessModeNone
return
}
e := db.GetEngine(ctx)
var is bool
if user != nil {
is, err = repo_model.IsCollaborator(ctx, repo.ID, user.ID)
if err != nil {
return perm, err
}
}
if err = repo.GetOwner(ctx); err != nil {
return
}
// Prevent strangers from checking out public repo of private organization/users
// Allow user if they are collaborator of a repo within a private user or a private organization but not a member of the organization itself
if !organization.HasOrgOrUserVisible(ctx, repo.Owner, user) && !is {
perm.AccessMode = perm_model.AccessModeNone
return
}
if err = repo.LoadUnits(ctx); err != nil {
return
}
perm.Units = repo.Units
// anonymous visit public repo
if user == nil {
perm.AccessMode = perm_model.AccessModeRead
return
}
// Admin or the owner has super access to the repository
if user.IsAdmin || user.ID == repo.OwnerID {
perm.AccessMode = perm_model.AccessModeOwner
return
}
// plain user
perm.AccessMode, err = accessLevel(e, user, repo)
if err != nil {
return
}
if err = repo.GetOwner(ctx); err != nil {
return
}
if !repo.Owner.IsOrganization() {
return
}
perm.UnitsMode = make(map[unit.Type]perm_model.AccessMode)
// Collaborators on organization
if is {
for _, u := range repo.Units {
perm.UnitsMode[u.Type] = perm.AccessMode
}
}
// get units mode from teams
teams, err := organization.GetUserRepoTeams(ctx, repo.OwnerID, user.ID, repo.ID)
if err != nil {
return
}
// if user in an owner team
for _, team := range teams {
if team.AccessMode >= perm_model.AccessModeAdmin {
perm.AccessMode = perm_model.AccessModeOwner
perm.UnitsMode = nil
return
}
}
for _, u := range repo.Units {
var found bool
for _, team := range teams {
teamMode := team.UnitAccessModeCtx(ctx, u.Type)
if teamMode > perm_model.AccessModeNone {
m := perm.UnitsMode[u.Type]
if m < teamMode {
perm.UnitsMode[u.Type] = teamMode
}
found = true
}
}
// for a public repo on an organization, a non-restricted user has read permission on non-team defined units.
if !found && !repo.IsPrivate && !user.IsRestricted {
if _, ok := perm.UnitsMode[u.Type]; !ok {
perm.UnitsMode[u.Type] = perm_model.AccessModeRead
}
}
}
// remove no permission units
perm.Units = make([]*repo_model.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
}
// IsUserRealRepoAdmin check if this user is real repo admin
func IsUserRealRepoAdmin(repo *repo_model.Repository, user *user_model.User) (bool, error) {
if repo.OwnerID == user.ID {
return true, nil
}
if err := repo.GetOwner(db.DefaultContext); err != nil {
return false, err
}
accessMode, err := accessLevel(db.GetEngine(db.DefaultContext), user, repo)
if err != nil {
return false, err
}
return accessMode >= perm_model.AccessModeAdmin, nil
}
// IsUserRepoAdmin return true if user has admin right of a repo
func IsUserRepoAdmin(repo *repo_model.Repository, user *user_model.User) (bool, error) {
return IsUserRepoAdminCtx(db.DefaultContext, repo, user)
}
// IsUserRepoAdminCtx return true if user has admin right of a repo
func IsUserRepoAdminCtx(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (bool, error) {
if user == nil || repo == nil {
return false, nil
}
if user.IsAdmin {
return true, nil
}
e := db.GetEngine(ctx)
mode, err := accessLevel(e, user, repo)
if err != nil {
return false, err
}
if mode >= perm_model.AccessModeAdmin {
return true, nil
}
teams, err := organization.GetUserRepoTeams(ctx, repo.OwnerID, user.ID, repo.ID)
if err != nil {
return false, err
}
for _, team := range teams {
if team.AccessMode >= perm_model.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_model.User, repo *repo_model.Repository) (perm_model.AccessMode, error) { //nolint
return AccessLevelUnit(user, repo, unit.TypeCode)
}
// AccessLevelUnit returns the Access a user has to a repository's. Will return NoneAccess if the
// user does not have access.
func AccessLevelUnit(user *user_model.User, repo *repo_model.Repository, unitType unit.Type) (perm_model.AccessMode, error) { //nolint
return accessLevelUnit(db.DefaultContext, user, repo, unitType)
}
func accessLevelUnit(ctx context.Context, user *user_model.User, repo *repo_model.Repository, unitType unit.Type) (perm_model.AccessMode, error) {
perm, err := GetUserRepoPermission(ctx, repo, user)
if err != nil {
return perm_model.AccessModeNone, err
}
return perm.UnitAccessMode(unitType), nil
}
// HasAccessUnit returns true if user has testMode to the unit of the repository
func HasAccessUnit(ctx context.Context, user *user_model.User, repo *repo_model.Repository, unitType unit.Type, testMode perm_model.AccessMode) (bool, error) {
mode, err := accessLevelUnit(ctx, user, repo, unitType)
return testMode <= mode, err
}
// CanBeAssigned return true if user can be assigned to issue or pull requests in repo
// Currently any write access (code, issues or pr's) is assignable, to match assignee list in user interface.
// FIXME: user could send PullRequest also could be assigned???
func CanBeAssigned(ctx context.Context, user *user_model.User, repo *repo_model.Repository, _ bool) (bool, error) {
if user.IsOrganization() {
return false, fmt.Errorf("Organization can't be added as assignee [user_id: %d, repo_id: %d]", user.ID, repo.ID)
}
perm, err := GetUserRepoPermission(ctx, repo, user)
if err != nil {
return false, err
}
return perm.CanAccessAny(perm_model.AccessModeWrite, unit.TypeCode, unit.TypeIssues, unit.TypePullRequests), nil
}
// HasAccess returns true if user has access to repo
func HasAccess(ctx context.Context, userID int64, repo *repo_model.Repository) (bool, error) {
var user *user_model.User
var err error
if userID > 0 {
user, err = user_model.GetUserByIDEngine(db.GetEngine(ctx), userID)
if err != nil {
return false, err
}
}
perm, err := GetUserRepoPermission(ctx, repo, user)
if err != nil {
return false, err
}
return perm.HasAccess(), nil
}
// getUsersWithAccessMode returns users that have at least given access mode to the repository.
func getUsersWithAccessMode(ctx context.Context, repo *repo_model.Repository, mode perm_model.AccessMode) (_ []*user_model.User, err error) {
if err = repo.GetOwner(ctx); err != nil {
return nil, err
}
e := db.GetEngine(ctx)
accesses := make([]*Access, 0, 10)
if err = e.Where("repo_id = ? AND mode >= ?", repo.ID, mode).Find(&accesses); err != nil {
return nil, err
}
// Leave a seat for owner itself to append later, but if owner is an organization
// and just waste 1 unit is cheaper than re-allocate memory once.
users := make([]*user_model.User, 0, len(accesses)+1)
if len(accesses) > 0 {
userIDs := make([]int64, len(accesses))
for i := 0; i < len(accesses); i++ {
userIDs[i] = accesses[i].UserID
}
if err = e.In("id", userIDs).Find(&users); err != nil {
return nil, err
}
}
if !repo.Owner.IsOrganization() {
users = append(users, repo.Owner)
}
return users, nil
}
// GetRepoReaders returns all users that have explicit read access or higher to the repository.
func GetRepoReaders(repo *repo_model.Repository) (_ []*user_model.User, err error) {
return getUsersWithAccessMode(db.DefaultContext, repo, perm_model.AccessModeRead)
}
// GetRepoWriters returns all users that have write access to the repository.
func GetRepoWriters(repo *repo_model.Repository) (_ []*user_model.User, err error) {
return getUsersWithAccessMode(db.DefaultContext, repo, perm_model.AccessModeWrite)
}
// IsRepoReader returns true if user has explicit read access or higher to the repository.
func IsRepoReader(ctx context.Context, repo *repo_model.Repository, userID int64) (bool, error) {
if repo.OwnerID == userID {
return true, nil
}
return db.GetEngine(ctx).Where("repo_id = ? AND user_id = ? AND mode >= ?", repo.ID, userID, perm_model.AccessModeRead).Get(&Access{})
}