2014-02-17 18:38:50 -05:00
// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
2014-02-17 23:57:23 +08:00
package models
2016-12-28 09:34:35 +08:00
import "fmt"
2015-02-24 00:27:22 -05:00
2016-11-26 01:07:57 +01:00
// AccessMode specifies the users access mode
2015-02-05 15:29:08 +02:00
type AccessMode int
2014-06-25 00:44:48 -04:00
2014-02-17 23:57:23 +08:00
const (
2016-11-26 01:07:57 +01:00
// AccessModeNone no access
AccessModeNone AccessMode = iota // 0
// AccessModeRead read access
AccessModeRead // 1
// AccessModeWrite write access
AccessModeWrite // 2
// AccessModeAdmin admin access
AccessModeAdmin // 3
// AccessModeOwner owner access
AccessModeOwner // 4
2014-02-17 23:57:23 +08:00
)
2016-03-21 12:47:54 -04:00
func ( mode AccessMode ) String ( ) string {
switch mode {
2016-11-07 17:20:37 +01:00
case AccessModeRead :
2016-03-21 12:47:54 -04:00
return "read"
2016-11-07 17:20:37 +01:00
case AccessModeWrite :
2016-03-21 12:47:54 -04:00
return "write"
2016-11-07 17:20:37 +01:00
case AccessModeAdmin :
2016-03-21 12:47:54 -04:00
return "admin"
2016-11-07 17:20:37 +01:00
case AccessModeOwner :
2016-03-21 12:47:54 -04:00
return "owner"
default :
return "none"
}
}
// ParseAccessMode returns corresponding access mode to given permission string.
func ParseAccessMode ( permission string ) AccessMode {
switch permission {
case "write" :
2016-11-07 17:20:37 +01:00
return AccessModeWrite
2016-03-21 12:47:54 -04:00
case "admin" :
2016-11-07 17:20:37 +01:00
return AccessModeAdmin
2016-03-21 12:47:54 -04:00
default :
2016-11-07 17:20:37 +01:00
return AccessModeRead
2016-03-21 12:47:54 -04:00
}
}
2015-02-05 15:29:08 +02:00
// 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 AccessMode
2014-02-17 23:57:23 +08:00
}
2017-03-14 20:51:46 -04:00
func accessLevel ( e Engine , userID int64 , repo * Repository ) ( AccessMode , error ) {
2016-11-07 17:20:37 +01:00
mode := AccessModeNone
2015-02-23 02:15:53 -05:00
if ! repo . IsPrivate {
2016-11-07 17:20:37 +01:00
mode = AccessModeRead
2015-02-11 21:58:37 -05:00
}
2014-05-01 11:32:12 -04:00
2017-03-14 20:51:46 -04:00
if userID == 0 {
2015-11-19 11:40:00 -05:00
return mode , nil
}
2014-04-04 18:55:17 -04:00
2017-03-14 20:51:46 -04:00
if userID == repo . OwnerID {
2016-11-07 17:20:37 +01:00
return AccessModeOwner , nil
2014-04-11 21:47:39 -04:00
}
2015-02-05 15:29:08 +02:00
2017-03-14 20:51:46 -04:00
a := & Access { UserID : userID , RepoID : repo . ID }
2015-11-19 11:40:00 -05:00
if has , err := e . Get ( a ) ; ! has || err != nil {
return mode , err
}
return a . Mode , nil
2014-02-17 23:57:23 +08:00
}
2015-01-23 09:54:16 +02:00
2017-01-04 19:57:54 -05:00
type repoAccess struct {
Access ` xorm:"extends" `
Repository ` xorm:"extends" `
}
func ( repoAccess ) TableName ( ) string {
return "access"
}
2015-11-13 17:37:02 -05:00
// GetRepositoryAccesses finds all repositories with their access mode where a user has access but does not own.
2016-11-26 01:07:57 +01:00
func ( user * User ) GetRepositoryAccesses ( ) ( map [ * Repository ] AccessMode , error ) {
2016-12-28 09:34:35 +08:00
rows , err := x .
2017-01-04 19:57:54 -05:00
Join ( "INNER" , "repository" , "repository.id = access.repo_id" ) .
2016-12-28 09:34:35 +08:00
Where ( "access.user_id = ?" , user . ID ) .
And ( "repository.owner_id <> ?" , user . ID ) .
2017-01-04 19:57:54 -05:00
Rows ( new ( repoAccess ) )
2016-12-28 09:34:35 +08:00
if err != nil {
2015-01-23 09:54:16 +02:00
return nil , err
}
2016-12-28 09:34:35 +08:00
defer rows . Close ( )
2015-01-23 09:54:16 +02:00
2017-01-04 19:57:54 -05:00
var repos = make ( map [ * Repository ] AccessMode , 10 )
var ownerCache = make ( map [ int64 ] * User , 10 )
2016-12-28 09:34:35 +08:00
for rows . Next ( ) {
2017-01-04 19:57:54 -05:00
var repo repoAccess
2016-12-28 09:34:35 +08:00
err = rows . Scan ( & repo )
2015-01-23 09:54:16 +02:00
if err != nil {
return nil , err
}
2016-12-28 09:34:35 +08:00
var ok bool
if repo . Owner , ok = ownerCache [ repo . OwnerID ] ; ! ok {
if err = repo . GetOwner ( ) ; err != nil {
return nil , err
}
ownerCache [ repo . OwnerID ] = repo . Owner
2015-02-04 16:08:55 +02:00
}
2016-12-28 09:34:35 +08:00
repos [ & repo . Repository ] = repo . Access . Mode
2015-01-23 09:54:16 +02:00
}
return repos , nil
}
2015-02-05 15:29:08 +02:00
2016-07-24 14:32:46 +08:00
// GetAccessibleRepositories finds repositories which the user has access but does not own.
// If limit is smaller than 1 means returns all found results.
func ( user * User ) GetAccessibleRepositories ( limit int ) ( repos [ ] * Repository , _ error ) {
2016-11-10 16:16:32 +01:00
sess := x .
Where ( "owner_id !=? " , user . ID ) .
Desc ( "updated_unix" )
2016-07-24 14:32:46 +08:00
if limit > 0 {
sess . Limit ( limit )
repos = make ( [ ] * Repository , 0 , limit )
} else {
repos = make ( [ ] * Repository , 0 , 10 )
2015-11-13 17:37:02 -05:00
}
2016-11-10 16:16:32 +01:00
return repos , sess .
Join ( "INNER" , "access" , "access.user_id = ? AND access.repo_id = repository.id" , user . ID ) .
Find ( & repos )
2015-11-13 17:37:02 -05:00
}
2015-02-13 00:58:46 -05:00
func maxAccessMode ( modes ... AccessMode ) AccessMode {
2016-11-07 17:20:37 +01:00
max := AccessModeNone
2015-02-13 00:58:46 -05:00
for _ , mode := range modes {
if mode > max {
max = mode
}
}
return max
}
2017-01-04 19:50:34 -05:00
// FIXME: do cross-comparison so reduce deletions and additions to the minimum?
2015-02-28 21:44:09 -05:00
func ( repo * Repository ) refreshAccesses ( e Engine , accessMap map [ int64 ] AccessMode ) ( err error ) {
2016-11-07 17:20:37 +01:00
minMode := AccessModeRead
2015-02-28 21:44:09 -05:00
if ! repo . IsPrivate {
2016-11-07 17:20:37 +01:00
minMode = AccessModeWrite
2015-02-28 21:44:09 -05:00
}
newAccesses := make ( [ ] Access , 0 , len ( accessMap ) )
for userID , mode := range accessMap {
if mode < minMode {
continue
}
newAccesses = append ( newAccesses , Access {
UserID : userID ,
2015-08-08 22:43:14 +08:00
RepoID : repo . ID ,
2015-02-28 21:44:09 -05:00
Mode : mode ,
} )
}
2015-02-13 02:56:42 -05:00
2015-02-28 21:44:09 -05:00
// Delete old accesses and insert new ones for repository.
2015-08-08 22:43:14 +08:00
if _ , err = e . Delete ( & Access { RepoID : repo . ID } ) ; err != nil {
2015-02-28 21:44:09 -05:00
return fmt . Errorf ( "delete old accesses: %v" , err )
} else if _ , err = e . Insert ( newAccesses ) ; err != nil {
return fmt . Errorf ( "insert new accesses: %v" , err )
}
2015-02-13 02:56:42 -05:00
return nil
}
2016-03-05 18:08:42 -05:00
// refreshCollaboratorAccesses retrieves repository collaborations with their access modes.
2015-02-28 21:44:09 -05:00
func ( repo * Repository ) refreshCollaboratorAccesses ( e Engine , accessMap map [ int64 ] AccessMode ) error {
2016-03-05 18:08:42 -05:00
collaborations , err := repo . getCollaborations ( e )
2015-02-05 15:29:08 +02:00
if err != nil {
2016-03-05 18:08:42 -05:00
return fmt . Errorf ( "getCollaborations: %v" , err )
2015-02-05 15:29:08 +02:00
}
2016-03-05 18:08:42 -05:00
for _ , c := range collaborations {
accessMap [ c . UserID ] = c . Mode
2015-02-05 15:29:08 +02:00
}
2015-02-28 21:44:09 -05:00
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 ( repo * Repository ) recalculateTeamAccesses ( e Engine , ignTeamID int64 ) ( err error ) {
accessMap := make ( map [ int64 ] AccessMode , 20 )
if err = repo . getOwner ( e ) ; err != nil {
2015-02-05 15:29:08 +02:00
return err
2015-08-28 13:51:15 +08:00
} else if ! repo . Owner . IsOrganization ( ) {
return fmt . Errorf ( "owner is not an organization: %d" , repo . OwnerID )
2015-02-05 15:29:08 +02:00
}
2015-08-28 13:51:15 +08:00
2015-07-24 16:52:01 +08:00
if err = repo . refreshCollaboratorAccesses ( e , accessMap ) ; err != nil {
return fmt . Errorf ( "refreshCollaboratorAccesses: %v" , err )
}
2015-02-05 15:29:08 +02:00
2015-08-28 13:51:15 +08:00
if err = repo . Owner . getTeams ( e ) ; err != nil {
return err
}
2015-03-24 18:14:04 -04:00
2015-08-28 13:51:15 +08:00
for _ , t := range repo . Owner . Teams {
if t . ID == ignTeamID {
continue
}
2015-02-05 15:29:08 +02:00
2015-08-28 13:51:15 +08:00
// Owner team gets owner access, and skip for teams that do not
// have relations with repository.
if t . IsOwnerTeam ( ) {
2016-11-07 17:20:37 +01:00
t . Authorize = AccessModeOwner
2015-08-28 13:51:15 +08:00
} else if ! t . hasRepository ( e , repo . ID ) {
continue
}
if err = t . getMembers ( e ) ; err != nil {
return fmt . Errorf ( "getMembers '%d': %v" , t . ID , err )
}
for _ , m := range t . Members {
2016-07-24 01:08:22 +08:00
accessMap [ m . ID ] = maxAccessMode ( accessMap [ m . ID ] , t . Authorize )
2015-02-05 15:29:08 +02:00
}
}
2015-02-28 21:44:09 -05:00
return repo . refreshAccesses ( e , accessMap )
}
2015-02-05 15:29:08 +02:00
2015-02-28 21:44:09 -05:00
func ( repo * Repository ) recalculateAccesses ( e Engine ) error {
2015-08-28 13:51:15 +08:00
if repo . Owner . IsOrganization ( ) {
return repo . recalculateTeamAccesses ( e , 0 )
}
2015-02-28 21:44:09 -05:00
accessMap := make ( map [ int64 ] AccessMode , 20 )
if err := repo . refreshCollaboratorAccesses ( e , accessMap ) ; err != nil {
return fmt . Errorf ( "refreshCollaboratorAccesses: %v" , err )
2015-02-05 15:29:08 +02:00
}
2015-02-28 21:44:09 -05:00
return repo . refreshAccesses ( e , accessMap )
2015-02-13 00:58:46 -05:00
}
2015-02-05 15:29:08 +02:00
2015-02-13 00:58:46 -05:00
// RecalculateAccesses recalculates all accesses for repository.
2016-11-26 01:07:57 +01:00
func ( repo * Repository ) RecalculateAccesses ( ) error {
return repo . recalculateAccesses ( x )
2015-02-05 15:29:08 +02:00
}