2018-11-28 14:26:14 +03:00
// 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
2019-04-22 23:40:51 +03:00
import (
"fmt"
"code.gitea.io/gitea/modules/log"
)
2018-11-28 14:26:14 +03:00
// 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 )
}
2019-04-22 23:40:51 +03:00
// 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 {
2019-07-23 21:50:39 +03:00
config = err . Error ( )
2019-04-22 23:40:51 +03:00
}
}
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 ... )
}
2018-11-28 14:26:14 +03:00
// 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 ) {
2019-04-22 23:40:51 +03:00
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 )
} ( )
}
2018-11-28 14:26:14 +03:00
// anonymous user visit private repo.
// TODO: anonymous user visit public unit of private repo???
if user == nil && repo . IsPrivate {
perm . AccessMode = AccessModeNone
return
}
2019-05-16 18:48:40 +03:00
var isCollaborator bool
if user != nil {
isCollaborator , err = repo . isCollaborator ( e , user . ID )
if err != nil {
return perm , err
}
}
2020-01-12 12:36:21 +03:00
if err = repo . getOwner ( e ) ; err != nil {
return
}
2019-05-16 18:48:40 +03:00
// 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
2020-10-10 22:48:58 +03:00
if repo . Owner . IsOrganization ( ) && ! hasOrgVisible ( e , repo . Owner , user ) && ! isCollaborator {
2019-04-25 21:59:10 +03:00
perm . AccessMode = AccessModeNone
return
}
2018-11-28 14:26:14 +03:00
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
2020-01-13 20:33:46 +03:00
perm . AccessMode , err = accessLevel ( e , user , repo )
2018-11-28 14:26:14 +03:00
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
2019-05-16 18:48:40 +03:00
if isCollaborator {
2018-11-28 14:26:14 +03:00
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
}
2019-02-22 19:14:45 +03:00
// if user in an owner team
for _ , team := range teams {
if team . Authorize >= AccessModeOwner {
perm . AccessMode = AccessModeOwner
perm . UnitsMode = nil
return
}
}
2018-11-28 14:26:14 +03:00
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
}
}
2020-01-13 20:33:46 +03:00
// 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 {
2018-11-28 14:26:14 +03:00
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
}
2020-01-11 10:29:34 +03:00
// IsUserRepoAdmin return true if user has admin right of a repo
2018-11-28 14:26:14 +03:00
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
}
2020-01-13 20:33:46 +03:00
mode , err := accessLevel ( e , user , repo )
2018-11-28 14:26:14 +03:00
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 )
}
2019-10-28 05:11:50 +03:00
// 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 )
}
2018-11-28 14:26:14 +03:00
func accessLevelUnit ( e Engine , user * User , repo * Repository , unitType UnitType ) ( AccessMode , error ) {
perm , err := getUserRepoPermission ( e , repo , user )
if err != nil {
return AccessModeNone , err
}
2019-03-24 12:08:45 +03:00
return perm . UnitAccessMode ( unitType ) , nil
2018-11-28 14:26:14 +03:00
}
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 )
}
2019-10-25 17:46:37 +03:00
// 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.
2018-11-28 14:26:14 +03:00
// FIXME: user could send PullRequest also could be assigned???
2019-10-25 17:46:37 +03:00
func CanBeAssigned ( user * User , repo * Repository , isPull bool ) ( bool , error ) {
2020-04-08 00:52:01 +03:00
return canBeAssigned ( x , user , repo , isPull )
}
func canBeAssigned ( e Engine , user * User , repo * Repository , _ bool ) ( bool , error ) {
2019-10-25 17:46:37 +03:00
if user . IsOrganization ( ) {
return false , fmt . Errorf ( "Organization can't be added as assignee [user_id: %d, repo_id: %d]" , user . ID , repo . ID )
}
2020-04-08 00:52:01 +03:00
perm , err := getUserRepoPermission ( e , repo , user )
2019-10-25 17:46:37 +03:00
if err != nil {
return false , err
}
return perm . CanAccessAny ( AccessModeWrite , UnitTypeCode , UnitTypeIssues , UnitTypePullRequests ) , nil
2018-11-28 14:26:14 +03:00
}
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 )
}
2020-01-05 04:23:29 +03:00
// 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
}