mirror of
				https://github.com/go-gitea/gitea
				synced 2025-11-03 12:58:29 +00:00 
			
		
		
		
	* remove a user from being assigned to any issue/PR if (s)he is removed as a collaborator * fix gender specific comment * do not remove users that still have access to the repo if they are a member of a team that can access the repo * add context to errors * updates * incorporate review fixes * Update models/repo_collaboration.go Co-Authored-By: 6543 <6543@obermui.de> * Update models/repo_collaboration.go Co-Authored-By: 6543 <6543@obermui.de> * Fix Rebase Relict * Fix & Impruve * use xorm builder * all in one session * generalize reconsiderIssueAssignees * Only Unwatch if have no access anymore * prepare for reuse * Same things if remove User from Team * fix lint * let mysql take time to react * add description * CI.restart() * CI.restart() Co-authored-by: Lanre Adelowo <yo@lanre.wtf> Co-authored-by: techknowlogick <matti@mdranta.net> Co-authored-by: Lauris BH <lauris@nix.lv>
		
			
				
	
	
		
			396 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			396 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// 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 (
 | 
						|
	"fmt"
 | 
						|
 | 
						|
	"code.gitea.io/gitea/modules/log"
 | 
						|
)
 | 
						|
 | 
						|
// 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)
 | 
						|
}
 | 
						|
 | 
						|
// ColorFormat writes a colored string for these Permissions
 | 
						|
func (p *Permission) ColorFormat(s fmt.State) {
 | 
						|
	noColor := log.ColorBytes(log.Reset)
 | 
						|
 | 
						|
	format := "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(repo *Repository, user *User) (Permission, error) {
 | 
						|
	return getUserRepoPermission(x, repo, user)
 | 
						|
}
 | 
						|
 | 
						|
func getUserRepoPermission(e Engine, repo *Repository, user *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 = AccessModeNone
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	var isCollaborator bool
 | 
						|
	if user != nil {
 | 
						|
		isCollaborator, err = repo.isCollaborator(e, user.ID)
 | 
						|
		if err != nil {
 | 
						|
			return perm, err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if err = repo.getOwner(e); err != nil {
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	// Prevent strangers from checking out public repo of private orginization
 | 
						|
	// Allow user if they are collaborator of a repo within a private orginization but not a member of the orginization itself
 | 
						|
	if repo.Owner.IsOrganization() && !HasOrgVisible(repo.Owner, user) && !isCollaborator {
 | 
						|
		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, 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 {
 | 
						|
		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
 | 
						|
	}
 | 
						|
 | 
						|
	// if user in an owner team
 | 
						|
	for _, team := range teams {
 | 
						|
		if team.Authorize >= AccessModeOwner {
 | 
						|
			perm.AccessMode = AccessModeOwner
 | 
						|
			perm.UnitsMode = 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, 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] = 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 true 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, 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)
 | 
						|
}
 | 
						|
 | 
						|
// 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, repo *Repository, unitType UnitType) (AccessMode, error) {
 | 
						|
	return accessLevelUnit(x, user, repo, unitType)
 | 
						|
}
 | 
						|
 | 
						|
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(unitType), 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 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(user *User, repo *Repository, isPull bool) (bool, error) {
 | 
						|
	return canBeAssigned(x, user, repo, isPull)
 | 
						|
}
 | 
						|
 | 
						|
func canBeAssigned(e Engine, user *User, repo *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(e, repo, user)
 | 
						|
	if err != nil {
 | 
						|
		return false, err
 | 
						|
	}
 | 
						|
	return perm.CanAccessAny(AccessModeWrite, UnitTypeCode, UnitTypeIssues, UnitTypePullRequests), nil
 | 
						|
}
 | 
						|
 | 
						|
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)
 | 
						|
}
 | 
						|
 | 
						|
// FilterOutRepoIdsWithoutUnitAccess filter out repos where user has no access to repositories
 | 
						|
func FilterOutRepoIdsWithoutUnitAccess(u *User, repoIDs []int64, units ...UnitType) ([]int64, error) {
 | 
						|
	i := 0
 | 
						|
	for _, rID := range repoIDs {
 | 
						|
		repo, err := GetRepositoryByID(rID)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		perm, err := GetUserRepoPermission(repo, u)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		if perm.CanReadAny(units...) {
 | 
						|
			repoIDs[i] = rID
 | 
						|
			i++
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return repoIDs[:i], nil
 | 
						|
}
 |