2016-03-26 01:04:02 +03:00
// Copyright 2016 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.
package models
import (
"errors"
"fmt"
"strings"
)
2016-11-28 04:30:08 +03:00
const ownerTeamName = "Owners"
2016-03-26 01:04:02 +03:00
// Team represents a organization team.
type Team struct {
ID int64 ` xorm:"pk autoincr" `
OrgID int64 ` xorm:"INDEX" `
LowerName string
Name string
Description string
Authorize AccessMode
Repos [ ] * Repository ` xorm:"-" `
Members [ ] * User ` xorm:"-" `
NumRepos int
NumMembers int
}
// IsOwnerTeam returns true if team is owner team.
func ( t * Team ) IsOwnerTeam ( ) bool {
2016-11-28 04:30:08 +03:00
return t . Name == ownerTeamName
2016-03-26 01:04:02 +03:00
}
2016-11-28 04:30:08 +03:00
// IsMember returns true if given user is a member of team.
func ( t * Team ) IsMember ( uID int64 ) bool {
return IsTeamMember ( t . OrgID , t . ID , uID )
2016-03-26 01:04:02 +03:00
}
func ( t * Team ) getRepositories ( e Engine ) ( err error ) {
teamRepos := make ( [ ] * TeamRepo , 0 , t . NumRepos )
2016-11-10 18:16:32 +03:00
if err = x .
Where ( "team_id=?" , t . ID ) .
Find ( & teamRepos ) ; err != nil {
2016-03-26 01:04:02 +03:00
return fmt . Errorf ( "get team-repos: %v" , err )
}
t . Repos = make ( [ ] * Repository , 0 , len ( teamRepos ) )
for i := range teamRepos {
repo , err := getRepositoryByID ( e , teamRepos [ i ] . RepoID )
if err != nil {
return fmt . Errorf ( "getRepositoryById(%d): %v" , teamRepos [ i ] . RepoID , err )
}
t . Repos = append ( t . Repos , repo )
}
return nil
}
// GetRepositories returns all repositories in team of organization.
func ( t * Team ) GetRepositories ( ) error {
return t . getRepositories ( x )
}
func ( t * Team ) getMembers ( e Engine ) ( err error ) {
t . Members , err = getTeamMembers ( e , t . ID )
return err
}
// GetMembers returns all members in team of organization.
func ( t * Team ) GetMembers ( ) ( err error ) {
return t . getMembers ( x )
}
// AddMember adds new membership of the team to the organization,
// the user will have membership to the organization automatically when needed.
2016-11-28 04:30:08 +03:00
func ( t * Team ) AddMember ( uID int64 ) error {
return AddTeamMember ( t . OrgID , t . ID , uID )
2016-03-26 01:04:02 +03:00
}
// RemoveMember removes member from team of organization.
2016-11-28 04:30:08 +03:00
func ( t * Team ) RemoveMember ( uID int64 ) error {
return RemoveTeamMember ( t . OrgID , t . ID , uID )
2016-03-26 01:04:02 +03:00
}
func ( t * Team ) hasRepository ( e Engine , repoID int64 ) bool {
return hasTeamRepo ( e , t . OrgID , t . ID , repoID )
}
// HasRepository returns true if given repository belong to team.
func ( t * Team ) HasRepository ( repoID int64 ) bool {
return t . hasRepository ( x , repoID )
}
func ( t * Team ) addRepository ( e Engine , repo * Repository ) ( err error ) {
if err = addTeamRepo ( e , t . OrgID , t . ID , repo . ID ) ; err != nil {
return err
}
t . NumRepos ++
if _ , err = e . Id ( t . ID ) . AllCols ( ) . Update ( t ) ; err != nil {
return fmt . Errorf ( "update team: %v" , err )
}
if err = repo . recalculateTeamAccesses ( e , 0 ) ; err != nil {
return fmt . Errorf ( "recalculateAccesses: %v" , err )
}
if err = t . getMembers ( e ) ; err != nil {
return fmt . Errorf ( "getMembers: %v" , err )
}
for _ , u := range t . Members {
2016-07-23 20:08:22 +03:00
if err = watchRepo ( e , u . ID , repo . ID , true ) ; err != nil {
2016-03-26 01:04:02 +03:00
return fmt . Errorf ( "watchRepo: %v" , err )
}
}
return nil
}
// AddRepository adds new repository to team of organization.
func ( t * Team ) AddRepository ( repo * Repository ) ( err error ) {
if repo . OwnerID != t . OrgID {
return errors . New ( "Repository does not belong to organization" )
} else if t . HasRepository ( repo . ID ) {
return nil
}
sess := x . NewSession ( )
defer sessionRelease ( sess )
if err = sess . Begin ( ) ; err != nil {
return err
}
if err = t . addRepository ( sess , repo ) ; err != nil {
return err
}
return sess . Commit ( )
}
func ( t * Team ) removeRepository ( e Engine , repo * Repository , recalculate bool ) ( err error ) {
if err = removeTeamRepo ( e , t . ID , repo . ID ) ; err != nil {
return err
}
t . NumRepos --
if _ , err = e . Id ( t . ID ) . AllCols ( ) . Update ( t ) ; err != nil {
return err
}
// Don't need to recalculate when delete a repository from organization.
if recalculate {
if err = repo . recalculateTeamAccesses ( e , t . ID ) ; err != nil {
return err
}
}
if err = t . getMembers ( e ) ; err != nil {
return fmt . Errorf ( "get team members: %v" , err )
}
for _ , u := range t . Members {
2016-11-07 19:20:37 +03:00
has , err := hasAccess ( e , u , repo , AccessModeRead )
2016-03-26 01:04:02 +03:00
if err != nil {
return err
} else if has {
continue
}
2016-07-23 20:08:22 +03:00
if err = watchRepo ( e , u . ID , repo . ID , false ) ; err != nil {
2016-03-26 01:04:02 +03:00
return err
}
}
return nil
}
// RemoveRepository removes repository from team of organization.
func ( t * Team ) RemoveRepository ( repoID int64 ) error {
if ! t . HasRepository ( repoID ) {
return nil
}
repo , err := GetRepositoryByID ( repoID )
if err != nil {
return err
}
sess := x . NewSession ( )
defer sessionRelease ( sess )
if err = sess . Begin ( ) ; err != nil {
return err
}
if err = t . removeRepository ( sess , repo , true ) ; err != nil {
return err
}
return sess . Commit ( )
}
2016-11-28 04:30:08 +03:00
// IsUsableTeamName tests if a name could be as team name
2016-11-06 12:07:03 +03:00
func IsUsableTeamName ( name string ) ( err error ) {
var reservedTeamNames = [ ] string { "new" }
for i := range reservedTeamNames {
if name == reservedTeamNames [ i ] {
return ErrNameReserved { name }
}
}
return nil
}
2016-03-26 01:04:02 +03:00
// NewTeam creates a record of new team.
// It's caller's responsibility to assign organization ID.
2016-11-06 12:07:03 +03:00
func NewTeam ( t * Team ) ( err error ) {
2016-03-26 01:04:02 +03:00
if len ( t . Name ) == 0 {
return errors . New ( "empty team name" )
}
2016-11-06 12:07:03 +03:00
if err = IsUsableTeamName ( t . Name ) ; err != nil {
return err
}
2016-03-26 01:04:02 +03:00
has , err := x . Id ( t . OrgID ) . Get ( new ( User ) )
if err != nil {
return err
} else if ! has {
return ErrOrgNotExist
}
t . LowerName = strings . ToLower ( t . Name )
2016-11-10 18:16:32 +03:00
has , err = x .
Where ( "org_id=?" , t . OrgID ) .
And ( "lower_name=?" , t . LowerName ) .
Get ( new ( Team ) )
2016-03-26 01:04:02 +03:00
if err != nil {
return err
} else if has {
return ErrTeamAlreadyExist { t . OrgID , t . LowerName }
}
sess := x . NewSession ( )
defer sess . Close ( )
if err = sess . Begin ( ) ; err != nil {
return err
}
if _ , err = sess . Insert ( t ) ; err != nil {
sess . Rollback ( )
return err
}
// Update organization number of teams.
if _ , err = sess . Exec ( "UPDATE `user` SET num_teams=num_teams+1 WHERE id = ?" , t . OrgID ) ; err != nil {
sess . Rollback ( )
return err
}
return sess . Commit ( )
}
2016-11-28 04:30:08 +03:00
func getTeam ( e Engine , orgID int64 , name string ) ( * Team , error ) {
2016-03-26 01:04:02 +03:00
t := & Team {
2016-11-28 04:30:08 +03:00
OrgID : orgID ,
2016-03-26 01:04:02 +03:00
LowerName : strings . ToLower ( name ) ,
}
has , err := e . Get ( t )
if err != nil {
return nil , err
} else if ! has {
return nil , ErrTeamNotExist
}
return t , nil
}
// GetTeam returns team by given team name and organization.
2016-11-28 04:30:08 +03:00
func GetTeam ( orgID int64 , name string ) ( * Team , error ) {
return getTeam ( x , orgID , name )
2016-03-26 01:04:02 +03:00
}
2016-11-28 04:30:08 +03:00
func getTeamByID ( e Engine , teamID int64 ) ( * Team , error ) {
2016-03-26 01:04:02 +03:00
t := new ( Team )
2016-11-28 04:30:08 +03:00
has , err := e . Id ( teamID ) . Get ( t )
2016-03-26 01:04:02 +03:00
if err != nil {
return nil , err
} else if ! has {
return nil , ErrTeamNotExist
}
return t , nil
}
// GetTeamByID returns team by given ID.
2016-11-28 04:30:08 +03:00
func GetTeamByID ( teamID int64 ) ( * Team , error ) {
return getTeamByID ( x , teamID )
2016-03-26 01:04:02 +03:00
}
// UpdateTeam updates information of team.
func UpdateTeam ( t * Team , authChanged bool ) ( err error ) {
if len ( t . Name ) == 0 {
return errors . New ( "empty team name" )
}
if len ( t . Description ) > 255 {
t . Description = t . Description [ : 255 ]
}
sess := x . NewSession ( )
defer sessionRelease ( sess )
if err = sess . Begin ( ) ; err != nil {
return err
}
t . LowerName = strings . ToLower ( t . Name )
2016-11-10 18:16:32 +03:00
has , err := x .
Where ( "org_id=?" , t . OrgID ) .
And ( "lower_name=?" , t . LowerName ) .
And ( "id!=?" , t . ID ) .
Get ( new ( Team ) )
2016-03-26 01:04:02 +03:00
if err != nil {
return err
} else if has {
return ErrTeamAlreadyExist { t . OrgID , t . LowerName }
}
if _ , err = sess . Id ( t . ID ) . AllCols ( ) . Update ( t ) ; err != nil {
return fmt . Errorf ( "update: %v" , err )
}
// Update access for team members if needed.
if authChanged {
if err = t . getRepositories ( sess ) ; err != nil {
return fmt . Errorf ( "getRepositories:%v" , err )
}
for _ , repo := range t . Repos {
if err = repo . recalculateTeamAccesses ( sess , 0 ) ; err != nil {
return fmt . Errorf ( "recalculateTeamAccesses: %v" , err )
}
}
}
return sess . Commit ( )
}
// DeleteTeam deletes given team.
// It's caller's responsibility to assign organization ID.
func DeleteTeam ( t * Team ) error {
if err := t . GetRepositories ( ) ; err != nil {
return err
}
// Get organization.
org , err := GetUserByID ( t . OrgID )
if err != nil {
return err
}
sess := x . NewSession ( )
defer sessionRelease ( sess )
if err = sess . Begin ( ) ; err != nil {
return err
}
// Delete all accesses.
for _ , repo := range t . Repos {
if err = repo . recalculateTeamAccesses ( sess , t . ID ) ; err != nil {
return err
}
}
// Delete team-user.
2016-11-10 18:16:32 +03:00
if _ , err = sess .
Where ( "org_id=?" , org . ID ) .
Where ( "team_id=?" , t . ID ) .
Delete ( new ( TeamUser ) ) ; err != nil {
2016-03-26 01:04:02 +03:00
return err
}
// Delete team.
if _ , err = sess . Id ( t . ID ) . Delete ( new ( Team ) ) ; err != nil {
return err
}
// Update organization number of teams.
if _ , err = sess . Exec ( "UPDATE `user` SET num_teams=num_teams-1 WHERE id=?" , t . OrgID ) ; err != nil {
return err
}
return sess . Commit ( )
}
// ___________ ____ ___
// \__ ___/___ _____ _____ | | \______ ___________
// | |_/ __ \\__ \ / \| | / ___// __ \_ __ \
// | |\ ___/ / __ \| Y Y \ | /\___ \\ ___/| | \/
// |____| \___ >____ /__|_| /______//____ >\___ >__|
// \/ \/ \/ \/ \/
// TeamUser represents an team-user relation.
type TeamUser struct {
ID int64 ` xorm:"pk autoincr" `
OrgID int64 ` xorm:"INDEX" `
TeamID int64 ` xorm:"UNIQUE(s)" `
2016-11-28 04:30:08 +03:00
UID int64 ` xorm:"UNIQUE(s)" `
2016-03-26 01:04:02 +03:00
}
2016-11-28 04:30:08 +03:00
func isTeamMember ( e Engine , orgID , teamID , uID int64 ) bool {
2016-11-10 18:16:32 +03:00
has , _ := e .
Where ( "org_id=?" , orgID ) .
And ( "team_id=?" , teamID ) .
2016-11-28 04:30:08 +03:00
And ( "uid=?" , uID ) .
2016-11-10 18:16:32 +03:00
Get ( new ( TeamUser ) )
2016-03-26 01:04:02 +03:00
return has
}
// IsTeamMember returns true if given user is a member of team.
2016-11-28 04:30:08 +03:00
func IsTeamMember ( orgID , teamID , uID int64 ) bool {
return isTeamMember ( x , orgID , teamID , uID )
2016-03-26 01:04:02 +03:00
}
func getTeamMembers ( e Engine , teamID int64 ) ( _ [ ] * User , err error ) {
teamUsers := make ( [ ] * TeamUser , 0 , 10 )
2016-11-10 18:16:32 +03:00
if err = e .
Where ( "team_id=?" , teamID ) .
Find ( & teamUsers ) ; err != nil {
2016-03-26 01:04:02 +03:00
return nil , fmt . Errorf ( "get team-users: %v" , err )
}
members := make ( [ ] * User , 0 , len ( teamUsers ) )
for i := range teamUsers {
member := new ( User )
2016-11-28 04:30:08 +03:00
if _ , err = e . Id ( teamUsers [ i ] . UID ) . Get ( member ) ; err != nil {
return nil , fmt . Errorf ( "get user '%d': %v" , teamUsers [ i ] . UID , err )
2016-03-26 01:04:02 +03:00
}
members = append ( members , member )
}
return members , nil
}
// GetTeamMembers returns all members in given team of organization.
func GetTeamMembers ( teamID int64 ) ( [ ] * User , error ) {
return getTeamMembers ( x , teamID )
}
2016-11-28 04:30:08 +03:00
func getUserTeams ( e Engine , orgID , uID int64 ) ( [ ] * Team , error ) {
2016-03-26 01:04:02 +03:00
tus := make ( [ ] * TeamUser , 0 , 5 )
2016-11-10 18:16:32 +03:00
if err := e .
2016-11-28 04:30:08 +03:00
Where ( "uid=?" , uID ) .
And ( "org_id=?" , orgID ) .
2016-11-10 18:16:32 +03:00
Find ( & tus ) ; err != nil {
2016-03-26 01:04:02 +03:00
return nil , err
}
ts := make ( [ ] * Team , len ( tus ) )
for i , tu := range tus {
t := new ( Team )
has , err := e . Id ( tu . TeamID ) . Get ( t )
if err != nil {
return nil , err
} else if ! has {
return nil , ErrTeamNotExist
}
ts [ i ] = t
}
return ts , nil
}
// GetUserTeams returns all teams that user belongs to in given organization.
2016-11-28 04:30:08 +03:00
func GetUserTeams ( orgID , uID int64 ) ( [ ] * Team , error ) {
return getUserTeams ( x , orgID , uID )
2016-03-26 01:04:02 +03:00
}
// AddTeamMember adds new membership of given team to given organization,
// the user will have membership to given organization automatically when needed.
2016-11-28 04:30:08 +03:00
func AddTeamMember ( orgID , teamID , uID int64 ) error {
if IsTeamMember ( orgID , teamID , uID ) {
2016-03-26 01:04:02 +03:00
return nil
}
2016-11-28 04:30:08 +03:00
if err := AddOrgUser ( orgID , uID ) ; err != nil {
2016-03-26 01:04:02 +03:00
return err
}
// Get team and its repositories.
t , err := GetTeamByID ( teamID )
if err != nil {
return err
}
t . NumMembers ++
if err = t . GetRepositories ( ) ; err != nil {
return err
}
sess := x . NewSession ( )
defer sessionRelease ( sess )
if err = sess . Begin ( ) ; err != nil {
return err
}
tu := & TeamUser {
2016-11-28 04:30:08 +03:00
UID : uID ,
2016-03-26 01:04:02 +03:00
OrgID : orgID ,
TeamID : teamID ,
}
if _ , err = sess . Insert ( tu ) ; err != nil {
return err
} else if _ , err = sess . Id ( t . ID ) . Update ( t ) ; err != nil {
return err
}
// Give access to team repositories.
for _ , repo := range t . Repos {
if err = repo . recalculateTeamAccesses ( sess , 0 ) ; err != nil {
return err
}
}
// We make sure it exists before.
ou := new ( OrgUser )
2016-11-10 18:16:32 +03:00
if _ , err = sess .
2016-11-28 04:30:08 +03:00
Where ( "uid = ?" , uID ) .
2016-11-10 18:16:32 +03:00
And ( "org_id = ?" , orgID ) .
Get ( ou ) ; err != nil {
2016-03-26 01:04:02 +03:00
return err
}
ou . NumTeams ++
if t . IsOwnerTeam ( ) {
ou . IsOwner = true
}
if _ , err = sess . Id ( ou . ID ) . AllCols ( ) . Update ( ou ) ; err != nil {
return err
}
return sess . Commit ( )
}
2016-11-28 04:30:08 +03:00
func removeTeamMember ( e Engine , orgID , teamID , uID int64 ) error {
if ! isTeamMember ( e , orgID , teamID , uID ) {
2016-03-26 01:04:02 +03:00
return nil
}
// Get team and its repositories.
t , err := getTeamByID ( e , teamID )
if err != nil {
return err
}
// Check if the user to delete is the last member in owner team.
if t . IsOwnerTeam ( ) && t . NumMembers == 1 {
2016-11-28 04:30:08 +03:00
return ErrLastOrgOwner { UID : uID }
2016-03-26 01:04:02 +03:00
}
t . NumMembers --
if err = t . getRepositories ( e ) ; err != nil {
return err
}
// Get organization.
org , err := getUserByID ( e , orgID )
if err != nil {
return err
}
tu := & TeamUser {
2016-11-28 04:30:08 +03:00
UID : uID ,
2016-03-26 01:04:02 +03:00
OrgID : orgID ,
TeamID : teamID ,
}
if _ , err := e . Delete ( tu ) ; err != nil {
return err
2016-11-10 18:16:32 +03:00
} else if _ , err = e .
Id ( t . ID ) .
AllCols ( ) .
Update ( t ) ; err != nil {
2016-03-26 01:04:02 +03:00
return err
}
// Delete access to team repositories.
for _ , repo := range t . Repos {
if err = repo . recalculateTeamAccesses ( e , 0 ) ; err != nil {
return err
}
}
// This must exist.
ou := new ( OrgUser )
2016-11-10 18:16:32 +03:00
_ , err = e .
2016-11-28 04:30:08 +03:00
Where ( "uid = ?" , uID ) .
2016-11-10 18:16:32 +03:00
And ( "org_id = ?" , org . ID ) .
Get ( ou )
2016-03-26 01:04:02 +03:00
if err != nil {
return err
}
ou . NumTeams --
if t . IsOwnerTeam ( ) {
ou . IsOwner = false
}
2016-11-10 18:16:32 +03:00
if _ , err = e .
Id ( ou . ID ) .
AllCols ( ) .
Update ( ou ) ; err != nil {
2016-03-26 01:04:02 +03:00
return err
}
return nil
}
// RemoveTeamMember removes member from given team of given organization.
2016-11-28 04:30:08 +03:00
func RemoveTeamMember ( orgID , teamID , uID int64 ) error {
2016-03-26 01:04:02 +03:00
sess := x . NewSession ( )
defer sessionRelease ( sess )
if err := sess . Begin ( ) ; err != nil {
return err
}
2016-11-28 04:30:08 +03:00
if err := removeTeamMember ( sess , orgID , teamID , uID ) ; err != nil {
2016-03-26 01:04:02 +03:00
return err
}
return sess . Commit ( )
}
// ___________ __________
// \__ ___/___ _____ _____\______ \ ____ ______ ____
// | |_/ __ \\__ \ / \| _// __ \\____ \ / _ \
// | |\ ___/ / __ \| Y Y \ | \ ___/| |_> > <_> )
// |____| \___ >____ /__|_| /____|_ /\___ > __/ \____/
// \/ \/ \/ \/ \/|__|
// TeamRepo represents an team-repository relation.
type TeamRepo struct {
ID int64 ` xorm:"pk autoincr" `
OrgID int64 ` xorm:"INDEX" `
TeamID int64 ` xorm:"UNIQUE(s)" `
RepoID int64 ` xorm:"UNIQUE(s)" `
}
func hasTeamRepo ( e Engine , orgID , teamID , repoID int64 ) bool {
2016-11-10 18:16:32 +03:00
has , _ := e .
Where ( "org_id=?" , orgID ) .
And ( "team_id=?" , teamID ) .
And ( "repo_id=?" , repoID ) .
Get ( new ( TeamRepo ) )
2016-03-26 01:04:02 +03:00
return has
}
// HasTeamRepo returns true if given repository belongs to team.
func HasTeamRepo ( orgID , teamID , repoID int64 ) bool {
return hasTeamRepo ( x , orgID , teamID , repoID )
}
func addTeamRepo ( e Engine , orgID , teamID , repoID int64 ) error {
_ , err := e . InsertOne ( & TeamRepo {
OrgID : orgID ,
TeamID : teamID ,
RepoID : repoID ,
} )
return err
}
// AddTeamRepo adds new repository relation to team.
func AddTeamRepo ( orgID , teamID , repoID int64 ) error {
return addTeamRepo ( x , orgID , teamID , repoID )
}
func removeTeamRepo ( e Engine , teamID , repoID int64 ) error {
_ , err := e . Delete ( & TeamRepo {
TeamID : teamID ,
RepoID : repoID ,
} )
return err
}
// RemoveTeamRepo deletes repository relation to team.
func RemoveTeamRepo ( teamID , repoID int64 ) error {
return removeTeamRepo ( x , teamID , repoID )
}