2022-03-29 09:29:02 +03:00
// Copyright 2018 The Gitea Authors. All rights reserved.
// Copyright 2016 The Gogs Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2022-03-29 09:29:02 +03:00
package organization
import (
"context"
"fmt"
"strings"
"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/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
2022-10-18 08:50:37 +03:00
"code.gitea.io/gitea/modules/util"
2022-03-29 09:29:02 +03:00
"xorm.io/builder"
)
// ___________
// \__ ___/___ _____ _____
// | |_/ __ \\__ \ / \
// | |\ ___/ / __ \| Y Y \
// |____| \___ >____ /__|_| /
// \/ \/ \/
// ErrTeamAlreadyExist represents a "TeamAlreadyExist" kind of error.
type ErrTeamAlreadyExist struct {
OrgID int64
Name string
}
// IsErrTeamAlreadyExist checks if an error is a ErrTeamAlreadyExist.
func IsErrTeamAlreadyExist ( err error ) bool {
_ , ok := err . ( ErrTeamAlreadyExist )
return ok
}
func ( err ErrTeamAlreadyExist ) Error ( ) string {
return fmt . Sprintf ( "team already exists [org_id: %d, name: %s]" , err . OrgID , err . Name )
}
2022-10-18 08:50:37 +03:00
func ( err ErrTeamAlreadyExist ) Unwrap ( ) error {
return util . ErrAlreadyExist
}
2022-03-29 09:29:02 +03:00
// ErrTeamNotExist represents a "TeamNotExist" error
type ErrTeamNotExist struct {
OrgID int64
TeamID int64
Name string
}
// IsErrTeamNotExist checks if an error is a ErrTeamNotExist.
func IsErrTeamNotExist ( err error ) bool {
_ , ok := err . ( ErrTeamNotExist )
return ok
}
func ( err ErrTeamNotExist ) Error ( ) string {
return fmt . Sprintf ( "team does not exist [org_id %d, team_id %d, name: %s]" , err . OrgID , err . TeamID , err . Name )
}
2022-10-18 08:50:37 +03:00
func ( err ErrTeamNotExist ) Unwrap ( ) error {
return util . ErrNotExist
}
2022-03-29 09:29:02 +03:00
// OwnerTeamName return the owner team name
const OwnerTeamName = "Owners"
// Team represents a organization team.
type Team struct {
ID int64 ` xorm:"pk autoincr" `
OrgID int64 ` xorm:"INDEX" `
LowerName string
Name string
Description string
AccessMode perm . AccessMode ` xorm:"'authorize'" `
Repos [ ] * repo_model . Repository ` xorm:"-" `
Members [ ] * user_model . User ` xorm:"-" `
NumRepos int
NumMembers int
Units [ ] * TeamUnit ` xorm:"-" `
IncludesAllRepositories bool ` xorm:"NOT NULL DEFAULT false" `
CanCreateOrgRepo bool ` xorm:"NOT NULL DEFAULT false" `
}
func init ( ) {
db . RegisterModel ( new ( Team ) )
db . RegisterModel ( new ( TeamUser ) )
db . RegisterModel ( new ( TeamRepo ) )
db . RegisterModel ( new ( TeamUnit ) )
2022-10-19 15:40:28 +03:00
db . RegisterModel ( new ( TeamInvite ) )
2022-03-29 09:29:02 +03:00
}
// SearchTeamOptions holds the search options
type SearchTeamOptions struct {
db . ListOptions
UserID int64
Keyword string
OrgID int64
IncludeDesc bool
}
2022-08-21 19:24:05 +03:00
func ( opts * SearchTeamOptions ) toCond ( ) builder . Cond {
2022-03-29 09:29:02 +03:00
cond := builder . NewCond ( )
if len ( opts . Keyword ) > 0 {
lowerKeyword := strings . ToLower ( opts . Keyword )
var keywordCond builder . Cond = builder . Like { "lower_name" , lowerKeyword }
if opts . IncludeDesc {
keywordCond = keywordCond . Or ( builder . Like { "LOWER(description)" , lowerKeyword } )
}
cond = cond . And ( keywordCond )
}
2022-08-21 19:24:05 +03:00
if opts . OrgID > 0 {
cond = cond . And ( builder . Eq { "`team`.org_id" : opts . OrgID } )
}
if opts . UserID > 0 {
cond = cond . And ( builder . Eq { "team_user.uid" : opts . UserID } )
}
return cond
}
2022-03-29 09:29:02 +03:00
2022-08-21 19:24:05 +03:00
// SearchTeam search for teams. Caller is responsible to check permissions.
func SearchTeam ( opts * SearchTeamOptions ) ( [ ] * Team , int64 , error ) {
2022-03-29 09:29:02 +03:00
sess := db . GetEngine ( db . DefaultContext )
2022-08-21 19:24:05 +03:00
opts . SetDefaultValues ( )
cond := opts . toCond ( )
if opts . UserID > 0 {
sess = sess . Join ( "INNER" , "team_user" , "team_user.team_id = team.id" )
}
2022-09-19 15:02:29 +03:00
sess = db . SetSessionPagination ( sess , opts )
2022-03-29 09:29:02 +03:00
teams := make ( [ ] * Team , 0 , opts . PageSize )
2022-09-19 15:02:29 +03:00
count , err := sess . Where ( cond ) . OrderBy ( "lower_name" ) . FindAndCount ( & teams )
if err != nil {
2022-03-29 09:29:02 +03:00
return nil , 0 , err
}
return teams , count , nil
}
// ColorFormat provides a basic color format for a Team
func ( t * Team ) ColorFormat ( s fmt . State ) {
if t == nil {
log . ColorFprintf ( s , "%d:%s (OrgID: %d) %-v" ,
log . NewColoredIDValue ( 0 ) ,
"<nil>" ,
log . NewColoredIDValue ( 0 ) ,
0 )
return
}
log . ColorFprintf ( s , "%d:%s (OrgID: %d) %-v" ,
log . NewColoredIDValue ( t . ID ) ,
t . Name ,
log . NewColoredIDValue ( t . OrgID ) ,
t . AccessMode )
}
// GetUnits return a list of available units for a team
func ( t * Team ) GetUnits ( ) error {
return t . getUnits ( db . DefaultContext )
}
func ( t * Team ) getUnits ( ctx context . Context ) ( err error ) {
if t . Units != nil {
return nil
}
t . Units , err = getUnitsByTeamID ( ctx , t . ID )
return err
}
// GetUnitNames returns the team units names
func ( t * Team ) GetUnitNames ( ) ( res [ ] string ) {
if t . AccessMode >= perm . AccessModeAdmin {
return unit . AllUnitKeyNames ( )
}
for _ , u := range t . Units {
res = append ( res , unit . Units [ u . Type ] . NameKey )
}
2022-06-20 13:02:49 +03:00
return res
2022-03-29 09:29:02 +03:00
}
// GetUnitsMap returns the team units permissions
func ( t * Team ) GetUnitsMap ( ) map [ string ] string {
m := make ( map [ string ] string )
if t . AccessMode >= perm . AccessModeAdmin {
for _ , u := range unit . Units {
m [ u . NameKey ] = t . AccessMode . String ( )
}
} else {
for _ , u := range t . Units {
m [ u . Unit ( ) . NameKey ] = u . AccessMode . String ( )
}
}
return m
}
// IsOwnerTeam returns true if team is owner team.
func ( t * Team ) IsOwnerTeam ( ) bool {
return t . Name == OwnerTeamName
}
// IsMember returns true if given user is a member of team.
func ( t * Team ) IsMember ( userID int64 ) bool {
isMember , err := IsTeamMember ( db . DefaultContext , t . OrgID , t . ID , userID )
if err != nil {
log . Error ( "IsMember: %v" , err )
return false
}
return isMember
}
2022-12-03 05:48:26 +03:00
// LoadRepositories returns paginated repositories in team of organization.
func ( t * Team ) LoadRepositories ( ctx context . Context ) ( err error ) {
2022-03-29 09:29:02 +03:00
if t . Repos != nil {
return nil
}
t . Repos , err = GetTeamRepositories ( ctx , & SearchTeamRepoOptions {
TeamID : t . ID ,
} )
2022-06-20 13:02:49 +03:00
return err
2022-03-29 09:29:02 +03:00
}
2022-12-03 05:48:26 +03:00
// LoadMembers returns paginated members in team of organization.
func ( t * Team ) LoadMembers ( ctx context . Context ) ( err error ) {
2022-03-29 09:29:02 +03:00
t . Members , err = GetTeamMembers ( ctx , & SearchMembersOptions {
TeamID : t . ID ,
} )
return err
}
// UnitEnabled returns if the team has the given unit type enabled
func ( t * Team ) UnitEnabled ( tp unit . Type ) bool {
2022-04-01 09:05:31 +03:00
return t . UnitAccessMode ( tp ) > perm . AccessModeNone
2022-03-29 09:29:02 +03:00
}
2022-04-01 09:05:31 +03:00
// UnitAccessMode returns if the team has the given unit type enabled
// it is called in templates, should not be replaced by `UnitAccessModeCtx(ctx ...)`
2022-12-03 05:48:26 +03:00
func ( t * Team ) UnitAccessMode ( tp unit . Type ) perm . AccessMode { // Notice: It will be used in template, don't remove it directly
2022-04-01 09:05:31 +03:00
return t . UnitAccessModeCtx ( db . DefaultContext , tp )
2022-03-29 09:29:02 +03:00
}
2022-04-01 09:05:31 +03:00
// UnitAccessModeCtx returns if the team has the given unit type enabled
func ( t * Team ) UnitAccessModeCtx ( ctx context . Context , tp unit . Type ) perm . AccessMode {
2022-03-29 09:29:02 +03:00
if err := t . getUnits ( ctx ) ; err != nil {
log . Warn ( "Error loading team (ID: %d) units: %s" , t . ID , err . Error ( ) )
}
for _ , unit := range t . Units {
if unit . Type == tp {
return unit . AccessMode
}
}
return perm . AccessModeNone
}
// IsUsableTeamName tests if a name could be as team name
func IsUsableTeamName ( name string ) error {
switch name {
case "new" :
return db . ErrNameReserved { Name : name }
default :
return nil
}
}
2022-05-20 17:08:52 +03:00
// GetTeam returns team by given team name and organization.
func GetTeam ( ctx context . Context , orgID int64 , name string ) ( * Team , error ) {
2022-03-29 09:29:02 +03:00
t := & Team {
OrgID : orgID ,
LowerName : strings . ToLower ( name ) ,
}
has , err := db . GetByBean ( ctx , t )
if err != nil {
return nil , err
} else if ! has {
return nil , ErrTeamNotExist { orgID , 0 , name }
}
return t , nil
}
// GetTeamIDsByNames returns a slice of team ids corresponds to names.
func GetTeamIDsByNames ( orgID int64 , names [ ] string , ignoreNonExistent bool ) ( [ ] int64 , error ) {
ids := make ( [ ] int64 , 0 , len ( names ) )
for _ , name := range names {
2022-05-20 17:08:52 +03:00
u , err := GetTeam ( db . DefaultContext , orgID , name )
2022-03-29 09:29:02 +03:00
if err != nil {
if ignoreNonExistent {
continue
} else {
return nil , err
}
}
ids = append ( ids , u . ID )
}
return ids , nil
}
// GetOwnerTeam returns team by given team name and organization.
func GetOwnerTeam ( ctx context . Context , orgID int64 ) ( * Team , error ) {
2022-05-20 17:08:52 +03:00
return GetTeam ( ctx , orgID , OwnerTeamName )
2022-03-29 09:29:02 +03:00
}
2022-05-20 17:08:52 +03:00
// GetTeamByID returns team by given ID.
func GetTeamByID ( ctx context . Context , teamID int64 ) ( * Team , error ) {
2022-03-29 09:29:02 +03:00
t := new ( Team )
has , err := db . GetEngine ( ctx ) . ID ( teamID ) . Get ( t )
if err != nil {
return nil , err
} else if ! has {
return nil , ErrTeamNotExist { 0 , teamID , "" }
}
return t , nil
}
// GetTeamNamesByID returns team's lower name from a list of team ids.
func GetTeamNamesByID ( teamIDs [ ] int64 ) ( [ ] string , error ) {
if len ( teamIDs ) == 0 {
return [ ] string { } , nil
}
var teamNames [ ] string
err := db . GetEngine ( db . DefaultContext ) . Table ( "team" ) .
Select ( "lower_name" ) .
In ( "id" , teamIDs ) .
Asc ( "name" ) .
Find ( & teamNames )
return teamNames , err
}
2022-05-20 17:08:52 +03:00
// GetRepoTeams gets the list of teams that has access to the repository
func GetRepoTeams ( ctx context . Context , repo * repo_model . Repository ) ( teams [ ] * Team , err error ) {
return teams , db . GetEngine ( ctx ) .
2022-03-29 09:29:02 +03:00
Join ( "INNER" , "team_repo" , "team_repo.team_id = team.id" ) .
Where ( "team.org_id = ?" , repo . OwnerID ) .
And ( "team_repo.repo_id=?" , repo . ID ) .
OrderBy ( "CASE WHEN name LIKE '" + OwnerTeamName + "' THEN '' ELSE name END" ) .
Find ( & teams )
}
2022-08-25 05:31:57 +03:00
// IncrTeamRepoNum increases the number of repos for the given team by 1
func IncrTeamRepoNum ( ctx context . Context , teamID int64 ) error {
_ , err := db . GetEngine ( ctx ) . Incr ( "num_repos" ) . ID ( teamID ) . Update ( new ( Team ) )
return err
}