2014-04-10 20:20:58 +02:00
// Copyright 2014 The Gogs Authors. All rights reserved.
2019-01-23 23:30:19 +01:00
// Copyright 2019 The Gitea Authors. All rights reserved.
2014-04-10 20:20:58 +02:00
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
import (
2014-09-23 21:30:04 +02:00
"container/list"
2019-12-15 10:51:28 +01:00
"context"
2014-04-10 20:20:58 +02:00
"crypto/sha256"
2016-12-03 06:49:17 +01:00
"crypto/subtle"
2014-04-10 20:20:58 +02:00
"encoding/hex"
"errors"
"fmt"
2019-05-28 17:45:54 +02:00
_ "image/jpeg" // Needed for jpeg support
2014-04-10 20:20:58 +02:00
"os"
"path/filepath"
2020-02-23 20:52:05 +01:00
"regexp"
2014-04-10 20:20:58 +02:00
"strings"
"time"
2016-07-23 12:58:18 +02:00
"unicode/utf8"
2014-04-10 20:20:58 +02:00
2016-11-10 17:24:48 +01:00
"code.gitea.io/gitea/modules/base"
2018-02-18 19:14:37 +01:00
"code.gitea.io/gitea/modules/generate"
2019-03-27 10:33:00 +01:00
"code.gitea.io/gitea/modules/git"
2016-11-10 17:24:48 +01:00
"code.gitea.io/gitea/modules/log"
2020-06-20 15:20:25 +02:00
"code.gitea.io/gitea/modules/public"
2016-11-10 17:24:48 +01:00
"code.gitea.io/gitea/modules/setting"
2020-10-14 15:07:51 +02:00
"code.gitea.io/gitea/modules/storage"
2019-02-18 17:00:27 +01:00
"code.gitea.io/gitea/modules/structs"
2019-05-11 12:21:34 +02:00
api "code.gitea.io/gitea/modules/structs"
2019-08-15 16:46:21 +02:00
"code.gitea.io/gitea/modules/timeutil"
2017-10-24 19:36:19 +02:00
"code.gitea.io/gitea/modules/util"
2019-02-18 17:00:27 +01:00
2019-08-23 18:40:30 +02:00
"github.com/unknwon/com"
2019-07-07 08:01:01 +02:00
"golang.org/x/crypto/argon2"
"golang.org/x/crypto/bcrypt"
2019-02-18 17:00:27 +01:00
"golang.org/x/crypto/pbkdf2"
2019-07-07 08:01:01 +02:00
"golang.org/x/crypto/scrypt"
2019-02-18 17:00:27 +01:00
"golang.org/x/crypto/ssh"
2019-06-23 17:22:43 +02:00
"xorm.io/builder"
2014-04-10 20:20:58 +02:00
)
2016-11-28 17:47:46 +01:00
// UserType defines the user type
2014-06-25 06:44:48 +02:00
type UserType int
2014-04-10 20:20:58 +02:00
const (
2016-11-28 17:47:46 +01:00
// UserTypeIndividual defines an individual user
2016-11-07 17:53:22 +01:00
UserTypeIndividual UserType = iota // Historic reason to make it starts at 0.
2016-11-28 17:47:46 +01:00
// UserTypeOrganization defines an organization
2016-11-07 17:53:22 +01:00
UserTypeOrganization
2014-04-10 20:20:58 +02:00
)
2019-07-07 08:01:01 +02:00
const (
algoBcrypt = "bcrypt"
algoScrypt = "scrypt"
algoArgon2 = "argon2"
algoPbkdf2 = "pbkdf2"
2019-08-29 16:05:42 +02:00
// EmailNotificationsEnabled indicates that the user would like to receive all email notifications
EmailNotificationsEnabled = "enabled"
// EmailNotificationsOnMention indicates that the user would like to be notified via email when mentioned.
EmailNotificationsOnMention = "onmention"
// EmailNotificationsDisabled indicates that the user would not like to be notified via email.
EmailNotificationsDisabled = "disabled"
2019-07-07 08:01:01 +02:00
)
2014-04-10 20:20:58 +02:00
var (
2016-11-28 17:47:46 +01:00
// ErrUserNotKeyOwner user does not own this key error
ErrUserNotKeyOwner = errors . New ( "User does not own this public key" )
// ErrEmailNotExist e-mail does not exist error
ErrEmailNotExist = errors . New ( "E-mail does not exist" )
// ErrEmailNotActivated e-mail address has not been activated error
ErrEmailNotActivated = errors . New ( "E-mail address has not been activated" )
// ErrUserNameIllegal user name contains illegal characters error
ErrUserNameIllegal = errors . New ( "User name contains illegal characters" )
// ErrLoginSourceNotActived login source is not actived error
2014-05-11 08:12:45 +02:00
ErrLoginSourceNotActived = errors . New ( "Login source is not actived" )
2016-11-28 17:47:46 +01:00
// ErrUnsupportedLoginType login source is unknown error
ErrUnsupportedLoginType = errors . New ( "Login source is unknown" )
2020-02-23 20:52:05 +01:00
// Characters prohibited in a user name (anything except A-Za-z0-9_.-)
alphaDashDotPattern = regexp . MustCompile ( ` [^\w-\.] ` )
2014-04-10 20:20:58 +02:00
)
// User represents the object of individual and member of organization.
type User struct {
2016-07-23 19:08:22 +02:00
ID int64 ` xorm:"pk autoincr" `
2014-12-17 09:26:19 +01:00
LowerName string ` xorm:"UNIQUE NOT NULL" `
Name string ` xorm:"UNIQUE NOT NULL" `
FullName string
2015-12-21 13:24:11 +01:00
// Email is the primary email address (to be used for communication)
2019-08-29 16:05:42 +02:00
Email string ` xorm:"NOT NULL" `
KeepEmailPrivate bool
EmailNotificationsPreference string ` xorm:"VARCHAR(20) NOT NULL DEFAULT 'enabled'" `
Passwd string ` xorm:"NOT NULL" `
2020-09-03 20:58:31 +02:00
PasswdHashAlgo string ` xorm:"NOT NULL DEFAULT 'argon2'" `
2018-09-13 14:04:25 +02:00
// MustChangePassword is an attribute that determines if a user
// is to change his/her password after registration.
MustChangePassword bool ` xorm:"NOT NULL DEFAULT false" `
LoginType LoginType
LoginSource int64 ` xorm:"NOT NULL DEFAULT 0" `
LoginName string
Type UserType
OwnedOrgs [ ] * User ` xorm:"-" `
Orgs [ ] * User ` xorm:"-" `
Repos [ ] * Repository ` xorm:"-" `
Location string
Website string
Rands string ` xorm:"VARCHAR(10)" `
Salt string ` xorm:"VARCHAR(10)" `
Language string ` xorm:"VARCHAR(5)" `
2019-03-19 03:28:10 +01:00
Description string
2016-03-10 01:53:30 +01:00
2019-08-15 16:46:21 +02:00
CreatedUnix timeutil . TimeStamp ` xorm:"INDEX created" `
UpdatedUnix timeutil . TimeStamp ` xorm:"INDEX updated" `
LastLoginUnix timeutil . TimeStamp ` xorm:"INDEX" `
2014-11-21 16:58:08 +01:00
2015-10-25 09:26:26 +01:00
// Remember visibility choice for convenience, true for private
2015-08-28 10:44:04 +02:00
LastRepoVisibility bool
2016-11-27 12:59:12 +01:00
// Maximum repository creation limit, -1 means use global default
2015-12-10 18:48:45 +01:00
MaxRepoCreation int ` xorm:"NOT NULL DEFAULT -1" `
2015-08-28 10:44:04 +02:00
2015-12-21 13:24:11 +01:00
// Permissions
2017-01-06 16:14:33 +01:00
IsActive bool ` xorm:"INDEX" ` // Activate primary email
2016-12-31 03:33:30 +01:00
IsAdmin bool
2020-01-13 18:33:46 +01:00
IsRestricted bool ` xorm:"NOT NULL DEFAULT false" `
2016-12-31 03:33:30 +01:00
AllowGitHook bool
AllowImportLocal bool // Allow migrate repository by local path
AllowCreateOrganization bool ` xorm:"DEFAULT true" `
2017-09-16 02:18:25 +02:00
ProhibitLogin bool ` xorm:"NOT NULL DEFAULT false" `
2014-11-21 16:58:08 +01:00
2015-12-21 13:24:11 +01:00
// Avatar
2014-11-21 16:58:08 +01:00
Avatar string ` xorm:"VARCHAR(2048) NOT NULL" `
AvatarEmail string ` xorm:"NOT NULL" `
UseCustomAvatar bool
2015-12-21 13:24:11 +01:00
// Counters
NumFollowers int
2015-12-22 01:09:28 +01:00
NumFollowing int ` xorm:"NOT NULL DEFAULT 0" `
2015-12-21 13:24:11 +01:00
NumStars int
NumRepos int
2014-06-25 06:44:48 +02:00
2015-12-21 13:24:11 +01:00
// For organization
2019-09-23 22:08:03 +02:00
NumTeams int
NumMembers int
Teams [ ] * Team ` xorm:"-" `
Members UserList ` xorm:"-" `
MembersIsPublic map [ int64 ] bool ` xorm:"-" `
Visibility structs . VisibleType ` xorm:"NOT NULL DEFAULT 0" `
RepoAdminChangeTeamAccess bool ` xorm:"NOT NULL DEFAULT false" `
2016-11-13 03:54:04 +01:00
// Preferences
2020-06-05 22:01:53 +02:00
DiffViewStyle string ` xorm:"NOT NULL DEFAULT ''" `
Theme string ` xorm:"NOT NULL DEFAULT ''" `
KeepActivityPrivate bool ` xorm:"NOT NULL DEFAULT false" `
2014-04-10 20:20:58 +02:00
}
2020-01-24 20:00:29 +01:00
// SearchOrganizationsOptions options to filter organizations
type SearchOrganizationsOptions struct {
ListOptions
All bool
}
2019-04-22 22:40:51 +02:00
// ColorFormat writes a colored string to identify this struct
func ( u * User ) ColorFormat ( s fmt . State ) {
log . ColorFprintf ( s , "%d:%s" ,
log . NewColoredIDValue ( u . ID ) ,
log . NewColoredValue ( u . Name ) )
}
2016-11-28 17:47:46 +01:00
// BeforeUpdate is invoked from XORM before updating this object.
2015-12-10 18:37:53 +01:00
func ( u * User ) BeforeUpdate ( ) {
2015-12-10 18:46:05 +01:00
if u . MaxRepoCreation < - 1 {
u . MaxRepoCreation = - 1
2015-12-10 18:37:53 +01:00
}
2018-01-13 10:45:16 +01:00
// Organization does not need email
u . Email = strings . ToLower ( u . Email )
if ! u . IsOrganization ( ) {
if len ( u . AvatarEmail ) == 0 {
u . AvatarEmail = u . Email
}
}
u . LowerName = strings . ToLower ( u . Name )
u . Location = base . TruncateString ( u . Location , 255 )
u . Website = base . TruncateString ( u . Website , 255 )
u . Description = base . TruncateString ( u . Description , 255 )
2015-12-10 18:37:53 +01:00
}
2019-01-09 18:22:57 +01:00
// AfterLoad is invoked from XORM after filling all the fields of this object.
func ( u * User ) AfterLoad ( ) {
if u . Theme == "" {
u . Theme = setting . UI . DefaultTheme
}
}
2016-11-28 17:47:46 +01:00
// SetLastLogin set time to last login
2016-11-09 11:53:45 +01:00
func ( u * User ) SetLastLogin ( ) {
2019-08-15 16:46:21 +02:00
u . LastLoginUnix = timeutil . TimeStampNow ( )
2016-11-09 11:53:45 +01:00
}
2016-11-28 17:47:46 +01:00
// UpdateDiffViewStyle updates the users diff view style
2016-11-13 03:54:04 +01:00
func ( u * User ) UpdateDiffViewStyle ( style string ) error {
u . DiffViewStyle = style
2017-08-12 16:18:44 +02:00
return UpdateUserCols ( u , "diff_view_style" )
2016-11-13 03:54:04 +01:00
}
2019-01-09 18:22:57 +01:00
// UpdateTheme updates a users' theme irrespective of the site wide theme
func ( u * User ) UpdateTheme ( themeName string ) error {
u . Theme = themeName
return UpdateUserCols ( u , "theme" )
}
2019-07-27 15:15:30 +02:00
// GetEmail returns an noreply email, if the user has set to keep his
2017-01-08 04:12:03 +01:00
// email address private, otherwise the primary email address.
2019-07-27 15:15:30 +02:00
func ( u * User ) GetEmail ( ) string {
2017-01-08 04:12:03 +01:00
if u . KeepEmailPrivate {
return fmt . Sprintf ( "%s@%s" , u . LowerName , setting . Service . NoReplyAddress )
}
return u . Email
}
2016-11-28 17:47:46 +01:00
// APIFormat converts a User to api.User
2016-08-14 12:32:24 +02:00
func ( u * User ) APIFormat ( ) * api . User {
2020-01-31 22:13:51 +01:00
if u == nil {
return nil
}
2016-08-14 12:32:24 +02:00
return & api . User {
ID : u . ID ,
UserName : u . Name ,
FullName : u . FullName ,
2019-07-27 15:15:30 +02:00
Email : u . GetEmail ( ) ,
2016-11-29 09:25:47 +01:00
AvatarURL : u . AvatarLink ( ) ,
2018-05-05 02:28:30 +02:00
Language : u . Language ,
2019-03-03 23:57:24 +01:00
IsAdmin : u . IsAdmin ,
2019-06-16 05:28:32 +02:00
LastLogin : u . LastLoginUnix . AsTime ( ) ,
Created : u . CreatedUnix . AsTime ( ) ,
2016-08-14 12:32:24 +02:00
}
}
2016-11-28 17:47:46 +01:00
// IsLocal returns true if user login type is LoginPlain.
2015-12-11 01:02:57 +01:00
func ( u * User ) IsLocal ( ) bool {
2016-11-07 17:30:04 +01:00
return u . LoginType <= LoginPlain
2015-12-11 01:02:57 +01:00
}
2017-02-22 08:14:37 +01:00
// IsOAuth2 returns true if user login type is LoginOAuth2.
func ( u * User ) IsOAuth2 ( ) bool {
return u . LoginType == LoginOAuth2
}
2015-11-16 16:14:12 +01:00
// HasForkedRepo checks if user has already forked a repository with given ID.
func ( u * User ) HasForkedRepo ( repoID int64 ) bool {
2016-07-23 19:08:22 +02:00
_ , has := HasForkedRepo ( u . ID , repoID )
2015-11-16 16:14:12 +01:00
return has
}
2017-05-24 02:27:08 +02:00
// MaxCreationLimit returns the number of repositories a user is allowed to create
func ( u * User ) MaxCreationLimit ( ) int {
2015-12-10 18:46:05 +01:00
if u . MaxRepoCreation <= - 1 {
2015-12-10 18:37:53 +01:00
return setting . Repository . MaxCreationLimit
}
return u . MaxRepoCreation
}
2016-11-28 17:47:46 +01:00
// CanCreateRepo returns if user login can create a repository
2020-04-30 17:11:56 +02:00
// NOTE: functions calling this assume a failure due to repository count limit; if new checks are added, those functions should be revised
2015-12-10 18:37:53 +01:00
func ( u * User ) CanCreateRepo ( ) bool {
2017-05-20 05:51:19 +02:00
if u . IsAdmin {
return true
}
2015-12-10 18:46:05 +01:00
if u . MaxRepoCreation <= - 1 {
2015-12-11 21:11:13 +01:00
if setting . Repository . MaxCreationLimit <= - 1 {
2015-12-10 22:27:47 +01:00
return true
}
2015-12-10 18:37:53 +01:00
return u . NumRepos < setting . Repository . MaxCreationLimit
}
return u . NumRepos < u . MaxRepoCreation
}
2016-12-31 03:33:30 +01:00
// CanCreateOrganization returns true if user can create organisation.
func ( u * User ) CanCreateOrganization ( ) bool {
2017-02-14 13:16:00 +01:00
return u . IsAdmin || ( u . AllowCreateOrganization && ! setting . Admin . DisableRegularOrgCreation )
2016-12-31 03:33:30 +01:00
}
2015-11-04 00:40:52 +01:00
// CanEditGitHook returns true if user can edit Git hooks.
func ( u * User ) CanEditGitHook ( ) bool {
2017-09-12 11:25:42 +02:00
return ! setting . DisableGitHooks && ( u . IsAdmin || u . AllowGitHook )
2015-11-04 00:40:52 +01:00
}
// CanImportLocal returns true if user can migrate repository by local path.
func ( u * User ) CanImportLocal ( ) bool {
2017-01-23 02:19:50 +01:00
if ! setting . ImportLocalPaths {
return false
}
2015-11-04 00:40:52 +01:00
return u . IsAdmin || u . AllowImportLocal
}
2014-07-26 06:24:27 +02:00
// DashboardLink returns the user dashboard page link.
func ( u * User ) DashboardLink ( ) string {
if u . IsOrganization ( ) {
2016-11-27 11:14:25 +01:00
return setting . AppSubURL + "/org/" + u . Name + "/dashboard/"
2014-07-26 06:24:27 +02:00
}
2016-11-27 11:14:25 +01:00
return setting . AppSubURL + "/"
2014-07-26 06:24:27 +02:00
}
2015-08-26 15:45:51 +02:00
// HomeLink returns the user or organization home page link.
2014-06-25 06:44:48 +02:00
func ( u * User ) HomeLink ( ) string {
2016-11-27 11:14:25 +01:00
return setting . AppSubURL + "/" + u . Name
2014-04-10 20:20:58 +02:00
}
2015-09-17 07:54:12 +02:00
2017-02-11 13:57:33 +01:00
// HTMLURL returns the user or organization's full link.
func ( u * User ) HTMLURL ( ) string {
return setting . AppURL + u . Name
}
2015-09-17 07:54:12 +02:00
// GenerateEmailActivateCode generates an activate code based on user information and given e-mail.
func ( u * User ) GenerateEmailActivateCode ( email string ) string {
code := base . CreateTimeLimitCode (
2016-07-23 19:08:22 +02:00
com . ToStr ( u . ID ) + email + u . LowerName + u . Passwd + u . Rands ,
2015-09-17 07:54:12 +02:00
setting . Service . ActiveCodeLives , nil )
// Add tail hex username
code += hex . EncodeToString ( [ ] byte ( u . LowerName ) )
return code
}
// GenerateActivateCode generates an activate code based on user information.
func ( u * User ) GenerateActivateCode ( ) string {
return u . GenerateEmailActivateCode ( u . Email )
}
2014-04-10 20:20:58 +02:00
2016-11-28 17:47:46 +01:00
// GetFollowers returns range of user's followers.
2020-01-24 20:00:29 +01:00
func ( u * User ) GetFollowers ( listOptions ListOptions ) ( [ ] * User , error ) {
2016-11-10 16:16:32 +01:00
sess := x .
2018-10-19 18:49:36 +02:00
Where ( "follow.follow_id=?" , u . ID ) .
Join ( "LEFT" , "follow" , "`user`.id=follow.user_id" )
2020-01-24 20:00:29 +01:00
if listOptions . Page != 0 {
sess = listOptions . setSessionPagination ( sess )
users := make ( [ ] * User , 0 , listOptions . PageSize )
return users , sess . Find ( & users )
}
users := make ( [ ] * User , 0 , 8 )
2015-12-21 13:24:11 +01:00
return users , sess . Find ( & users )
}
2016-11-28 17:47:46 +01:00
// IsFollowing returns true if user is following followID.
2015-12-21 13:24:11 +01:00
func ( u * User ) IsFollowing ( followID int64 ) bool {
2016-07-23 19:08:22 +02:00
return IsFollowing ( u . ID , followID )
2015-12-21 13:24:11 +01:00
}
// GetFollowing returns range of user's following.
2020-01-24 20:00:29 +01:00
func ( u * User ) GetFollowing ( listOptions ListOptions ) ( [ ] * User , error ) {
2016-11-10 16:16:32 +01:00
sess := x .
2018-10-19 18:49:36 +02:00
Where ( "follow.user_id=?" , u . ID ) .
Join ( "LEFT" , "follow" , "`user`.id=follow.follow_id" )
2020-01-24 20:00:29 +01:00
if listOptions . Page != 0 {
sess = listOptions . setSessionPagination ( sess )
users := make ( [ ] * User , 0 , listOptions . PageSize )
return users , sess . Find ( & users )
}
users := make ( [ ] * User , 0 , 8 )
2015-12-21 13:24:11 +01:00
return users , sess . Find ( & users )
}
2014-04-10 20:20:58 +02:00
// NewGitSig generates and returns the signature of given user.
2014-06-25 06:44:48 +02:00
func ( u * User ) NewGitSig ( ) * git . Signature {
2014-04-10 20:20:58 +02:00
return & git . Signature {
2019-01-24 15:12:17 +01:00
Name : u . GitName ( ) ,
2019-07-27 15:15:30 +02:00
Email : u . GetEmail ( ) ,
2014-04-10 20:20:58 +02:00
When : time . Now ( ) ,
}
}
2019-07-07 08:01:01 +02:00
func hashPassword ( passwd , salt , algo string ) string {
var tempPasswd [ ] byte
switch algo {
case algoBcrypt :
tempPasswd , _ = bcrypt . GenerateFromPassword ( [ ] byte ( passwd ) , bcrypt . DefaultCost )
return string ( tempPasswd )
case algoScrypt :
tempPasswd , _ = scrypt . Key ( [ ] byte ( passwd ) , [ ] byte ( salt ) , 65536 , 16 , 2 , 50 )
case algoArgon2 :
tempPasswd = argon2 . IDKey ( [ ] byte ( passwd ) , [ ] byte ( salt ) , 2 , 65536 , 8 , 50 )
case algoPbkdf2 :
fallthrough
default :
tempPasswd = pbkdf2 . Key ( [ ] byte ( passwd ) , [ ] byte ( salt ) , 10000 , 50 , sha256 . New )
}
2018-01-11 23:19:38 +01:00
return fmt . Sprintf ( "%x" , tempPasswd )
}
2019-07-07 08:01:01 +02:00
// HashPassword hashes a password using the algorithm defined in the config value of PASSWORD_HASH_ALGO.
2018-01-11 23:19:38 +01:00
func ( u * User ) HashPassword ( passwd string ) {
2019-07-07 08:01:01 +02:00
u . PasswdHashAlgo = setting . PasswordHashAlgo
u . Passwd = hashPassword ( passwd , u . Salt , setting . PasswordHashAlgo )
2014-06-25 06:44:48 +02:00
}
2015-04-16 20:40:39 +02:00
// ValidatePassword checks if given password matches the one belongs to the user.
2015-04-16 20:36:32 +02:00
func ( u * User ) ValidatePassword ( passwd string ) bool {
2019-07-07 08:01:01 +02:00
tempHash := hashPassword ( passwd , u . Salt , u . PasswdHashAlgo )
if u . PasswdHashAlgo != algoBcrypt && subtle . ConstantTimeCompare ( [ ] byte ( u . Passwd ) , [ ] byte ( tempHash ) ) == 1 {
return true
}
if u . PasswdHashAlgo == algoBcrypt && bcrypt . CompareHashAndPassword ( [ ] byte ( u . Passwd ) , [ ] byte ( passwd ) ) == nil {
return true
}
return false
2014-08-02 19:47:33 +02:00
}
2017-02-22 08:14:37 +01:00
// IsPasswordSet checks if the password is set or left empty
func ( u * User ) IsPasswordSet ( ) bool {
2020-01-11 11:50:05 +01:00
return ! u . ValidatePassword ( "" )
2017-02-22 08:14:37 +01:00
}
2014-06-28 06:40:07 +02:00
// IsOrganization returns true if user is actually a organization.
2014-06-25 06:44:48 +02:00
func ( u * User ) IsOrganization ( ) bool {
2016-11-07 17:53:22 +01:00
return u . Type == UserTypeOrganization
2014-06-25 06:44:48 +02:00
}
2014-08-15 12:29:41 +02:00
// IsUserOrgOwner returns true if user is in the owner team of given organization.
2016-11-28 17:47:46 +01:00
func ( u * User ) IsUserOrgOwner ( orgID int64 ) bool {
2017-12-21 08:43:26 +01:00
isOwner , err := IsOrganizationOwner ( orgID , u . ID )
if err != nil {
2019-04-02 09:48:31 +02:00
log . Error ( "IsOrganizationOwner: %v" , err )
2017-12-21 08:43:26 +01:00
return false
}
return isOwner
2014-08-15 12:29:41 +02:00
}
2020-08-16 22:27:08 +02:00
// HasMemberWithUserID returns true if user with userID is part of the u organisation.
func ( u * User ) HasMemberWithUserID ( userID int64 ) bool {
return u . hasMemberWithUserID ( x , userID )
2019-04-25 20:59:10 +02:00
}
2020-08-16 22:27:08 +02:00
func ( u * User ) hasMemberWithUserID ( e Engine , userID int64 ) bool {
2019-04-25 20:59:10 +02:00
isMember , err := isOrganizationMember ( e , u . ID , userID )
2019-02-18 17:00:27 +01:00
if err != nil {
2019-04-02 09:48:31 +02:00
log . Error ( "IsOrganizationMember: %v" , err )
2019-02-18 17:00:27 +01:00
return false
}
return isMember
}
2017-01-20 06:16:10 +01:00
// IsPublicMember returns true if user public his/her membership in given organization.
2016-11-28 17:47:46 +01:00
func ( u * User ) IsPublicMember ( orgID int64 ) bool {
2017-12-21 08:43:26 +01:00
isMember , err := IsPublicMembership ( orgID , u . ID )
if err != nil {
2019-04-02 09:48:31 +02:00
log . Error ( "IsPublicMembership: %v" , err )
2017-12-21 08:43:26 +01:00
return false
}
return isMember
2014-08-15 12:29:41 +02:00
}
2015-09-06 14:54:08 +02:00
func ( u * User ) getOrganizationCount ( e Engine ) ( int64 , error ) {
2016-11-10 16:16:32 +01:00
return e .
Where ( "uid=?" , u . ID ) .
Count ( new ( OrgUser ) )
2015-09-06 14:54:08 +02:00
}
2014-06-28 21:43:25 +02:00
// GetOrganizationCount returns count of membership of organization of user.
func ( u * User ) GetOrganizationCount ( ) ( int64 , error ) {
2015-09-06 14:54:08 +02:00
return u . getOrganizationCount ( x )
2014-06-28 21:43:25 +02:00
}
2016-07-24 08:32:46 +02:00
// GetRepositories returns repositories that user owns, including private repositories.
2020-09-25 06:09:23 +02:00
func ( u * User ) GetRepositories ( listOpts ListOptions , names ... string ) ( err error ) {
u . Repos , _ , err = GetUserRepositories ( & SearchRepoOptions { Actor : u , Private : true , ListOptions : listOpts , LowerNames : names } )
2014-08-24 15:09:05 +02:00
return err
}
2019-10-08 19:55:16 +02:00
// GetRepositoryIDs returns repositories IDs where user owned and has unittypes
2020-01-17 08:34:37 +01:00
// Caller shall check that units is not globally disabled
2019-10-08 19:55:16 +02:00
func ( u * User ) GetRepositoryIDs ( units ... UnitType ) ( [ ] int64 , error ) {
var ids [ ] int64
sess := x . Table ( "repository" ) . Cols ( "repository.id" )
2018-06-21 18:00:13 +02:00
if len ( units ) > 0 {
2019-10-08 19:55:16 +02:00
sess = sess . Join ( "INNER" , "repo_unit" , "repository.id = repo_unit.repo_id" )
sess = sess . In ( "repo_unit.type" , units )
2018-06-21 18:00:13 +02:00
}
2019-10-08 19:55:16 +02:00
return ids , sess . Where ( "owner_id = ?" , u . ID ) . Find ( & ids )
2017-02-17 01:58:19 +01:00
}
2019-10-08 19:55:16 +02:00
// GetOrgRepositoryIDs returns repositories IDs where user's team owned and has unittypes
2020-01-17 08:34:37 +01:00
// Caller shall check that units is not globally disabled
2019-10-08 19:55:16 +02:00
func ( u * User ) GetOrgRepositoryIDs ( units ... UnitType ) ( [ ] int64 , error ) {
var ids [ ] int64
2020-01-05 02:23:29 +01:00
if err := x . Table ( "repository" ) .
2019-10-08 19:55:16 +02:00
Cols ( "repository.id" ) .
Join ( "INNER" , "team_user" , "repository.owner_id = team_user.org_id" ) .
2020-01-13 18:33:46 +01:00
Join ( "INNER" , "team_repo" , "(? != ? and repository.is_private != ?) OR (team_user.team_id = team_repo.team_id AND repository.id = team_repo.repo_id)" , true , u . IsRestricted , true ) .
2020-01-05 02:23:29 +01:00
Where ( "team_user.uid = ?" , u . ID ) .
GroupBy ( "repository.id" ) . Find ( & ids ) ; err != nil {
return nil , err
}
2019-10-08 19:55:16 +02:00
2018-06-21 18:00:13 +02:00
if len ( units ) > 0 {
2020-01-05 02:23:29 +01:00
return FilterOutRepoIdsWithoutUnitAccess ( u , ids , units ... )
2019-10-08 19:55:16 +02:00
}
2020-01-05 02:23:29 +01:00
return ids , nil
2019-10-08 19:55:16 +02:00
}
// GetAccessRepoIDs returns all repositories IDs where user's or user is a team member organizations
2020-01-17 08:34:37 +01:00
// Caller shall check that units is not globally disabled
2019-10-08 19:55:16 +02:00
func ( u * User ) GetAccessRepoIDs ( units ... UnitType ) ( [ ] int64 , error ) {
ids , err := u . GetRepositoryIDs ( units ... )
if err != nil {
return nil , err
}
ids2 , err := u . GetOrgRepositoryIDs ( units ... )
if err != nil {
return nil , err
}
return append ( ids , ids2 ... ) , nil
2017-02-17 01:58:19 +01:00
}
2016-11-28 17:47:46 +01:00
// GetMirrorRepositories returns mirror repositories that user owns, including private repositories.
2016-07-24 08:32:46 +02:00
func ( u * User ) GetMirrorRepositories ( ) ( [ ] * Repository , error ) {
return GetUserMirrorRepositories ( u . ID )
}
2015-09-07 19:58:23 +02:00
// GetOwnedOrganizations returns all organizations that user owns.
func ( u * User ) GetOwnedOrganizations ( ) ( err error ) {
2016-07-23 19:08:22 +02:00
u . OwnedOrgs , err = GetOwnedOrgsByUserID ( u . ID )
2015-09-07 19:58:23 +02:00
return err
}
2020-01-24 20:00:29 +01:00
// GetOrganizations returns paginated organizations that user belongs to.
2020-12-17 22:32:24 +01:00
// TODO: does not respect All and show orgs you privately participate
2020-01-24 20:00:29 +01:00
func ( u * User ) GetOrganizations ( opts * SearchOrganizationsOptions ) error {
2020-05-12 00:04:08 +02:00
sess := x . NewSession ( )
defer sess . Close ( )
schema , err := x . TableInfo ( new ( User ) )
2014-06-25 06:44:48 +02:00
if err != nil {
return err
}
2020-05-12 00:04:08 +02:00
groupByCols := & strings . Builder { }
for _ , col := range schema . Columns ( ) {
fmt . Fprintf ( groupByCols , "`%s`.%s," , schema . Name , col . Name )
}
groupByStr := groupByCols . String ( )
groupByStr = groupByStr [ 0 : len ( groupByStr ) - 1 ]
sess . Select ( "`user`.*, count(repo_id) as org_count" ) .
Table ( "user" ) .
Join ( "INNER" , "org_user" , "`org_user`.org_id=`user`.id" ) .
Join ( "LEFT" , builder .
Select ( "id as repo_id, owner_id as repo_owner_id" ) .
From ( "repository" ) .
Where ( accessibleRepositoryCondition ( u ) ) , "`repository`.repo_owner_id = `org_user`.org_id" ) .
And ( "`org_user`.uid=?" , u . ID ) .
GroupBy ( groupByStr )
if opts . PageSize != 0 {
sess = opts . setSessionPagination ( sess )
}
type OrgCount struct {
User ` xorm:"extends" `
OrgCount int
}
orgCounts := make ( [ ] * OrgCount , 0 , 10 )
if err := sess .
Asc ( "`user`.name" ) .
Find ( & orgCounts ) ; err != nil {
return err
}
2014-06-25 06:44:48 +02:00
2020-05-12 00:04:08 +02:00
orgs := make ( [ ] * User , len ( orgCounts ) )
for i , orgCount := range orgCounts {
orgCount . User . NumRepos = orgCount . OrgCount
orgs [ i ] = & orgCount . User
2014-06-25 06:44:48 +02:00
}
2020-05-12 00:04:08 +02:00
u . Orgs = orgs
2014-06-25 06:44:48 +02:00
return nil
2014-04-10 20:20:58 +02:00
}
2015-08-27 07:26:38 +02:00
// DisplayName returns full name if it's not empty,
// returns username otherwise.
func ( u * User ) DisplayName ( ) string {
2019-01-24 15:12:17 +01:00
trimmed := strings . TrimSpace ( u . FullName )
if len ( trimmed ) > 0 {
return trimmed
2014-09-17 15:11:51 +02:00
}
2015-08-27 07:26:38 +02:00
return u . Name
2014-09-17 15:11:51 +02:00
}
2019-05-08 10:41:35 +02:00
// GetDisplayName returns full name if it's not empty and DEFAULT_SHOW_FULL_NAME is set,
// returns username otherwise.
func ( u * User ) GetDisplayName ( ) string {
2020-02-26 23:08:24 +01:00
if setting . UI . DefaultShowFullName {
trimmed := strings . TrimSpace ( u . FullName )
if len ( trimmed ) > 0 {
return trimmed
}
2019-05-08 10:41:35 +02:00
}
return u . Name
}
2019-01-24 15:12:17 +01:00
func gitSafeName ( name string ) string {
return strings . TrimSpace ( strings . NewReplacer ( "\n" , "" , "<" , "" , ">" , "" ) . Replace ( name ) )
}
// GitName returns a git safe name
func ( u * User ) GitName ( ) string {
gitName := gitSafeName ( u . FullName )
if len ( gitName ) > 0 {
return gitName
}
// Although u.Name should be safe if created in our system
// LDAP users may have bad names
gitName = gitSafeName ( u . Name )
if len ( gitName ) > 0 {
return gitName
}
// Totally pathological name so it's got to be:
return fmt . Sprintf ( "user-%d" , u . ID )
}
2016-11-28 17:47:46 +01:00
// ShortName ellipses username to length
2015-11-18 23:42:20 +01:00
func ( u * User ) ShortName ( length int ) string {
2016-01-11 13:41:43 +01:00
return base . EllipsisString ( u . Name , length )
2015-11-18 23:42:20 +01:00
}
2017-03-15 01:52:01 +01:00
// IsMailable checks if a user is eligible
2017-02-02 13:33:36 +01:00
// to receive emails.
func ( u * User ) IsMailable ( ) bool {
return u . IsActive
}
2019-08-29 16:05:42 +02:00
// EmailNotifications returns the User's email notification preference
func ( u * User ) EmailNotifications ( ) string {
return u . EmailNotificationsPreference
}
// SetEmailNotifications sets the user's email notification preference
func ( u * User ) SetEmailNotifications ( set string ) error {
u . EmailNotificationsPreference = set
if err := UpdateUserCols ( u , "email_notifications_preference" ) ; err != nil {
log . Error ( "SetEmailNotifications: %v" , err )
return err
}
return nil
}
2017-10-03 08:29:26 +02:00
func isUserExist ( e Engine , uid int64 , name string ) ( bool , error ) {
2014-04-10 20:20:58 +02:00
if len ( name ) == 0 {
return false , nil
}
2017-10-03 08:29:26 +02:00
return e .
2016-11-10 16:16:32 +01:00
Where ( "id!=?" , uid ) .
Get ( & User { LowerName : strings . ToLower ( name ) } )
2014-04-10 20:20:58 +02:00
}
2017-10-03 08:29:26 +02:00
// IsUserExist checks if given user name exist,
// the user name should be noncased unique.
// If uid is presented, then check will rule out that one,
// it is used when update a user name in settings page.
func IsUserExist ( uid int64 , name string ) ( bool , error ) {
return isUserExist ( x , uid , name )
}
2017-03-15 01:52:01 +01:00
// GetUserSalt returns a random user salt token.
2016-12-20 13:32:02 +01:00
func GetUserSalt ( ) ( string , error ) {
2018-02-18 19:14:37 +01:00
return generate . GetRandomString ( 10 )
2014-04-10 20:20:58 +02:00
}
2016-08-14 12:32:24 +02:00
// NewGhostUser creates and returns a fake user for someone has deleted his/her account.
func NewGhostUser ( ) * User {
2015-08-14 20:48:05 +02:00
return & User {
2016-07-23 19:08:22 +02:00
ID : - 1 ,
2016-08-14 12:32:24 +02:00
Name : "Ghost" ,
LowerName : "ghost" ,
2015-08-14 20:48:05 +02:00
}
}
2020-01-15 12:14:07 +01:00
// NewReplaceUser creates and returns a fake user for external user
func NewReplaceUser ( name string ) * User {
return & User {
ID : - 1 ,
Name : name ,
LowerName : strings . ToLower ( name ) ,
}
}
2020-01-01 23:51:10 +01:00
// IsGhost check if user is fake user for a deleted account
func ( u * User ) IsGhost ( ) bool {
if u == nil {
return false
}
return u . ID == - 1 && u . Name == "Ghost"
}
2016-07-23 12:58:18 +02:00
var (
2020-06-20 15:20:25 +02:00
reservedUsernames = append ( [ ] string {
2020-04-18 23:01:06 +02:00
"." ,
".." ,
".well-known" ,
2018-08-13 07:02:18 +02:00
"admin" ,
"api" ,
"assets" ,
2020-04-18 23:01:06 +02:00
"attachments" ,
2018-08-13 07:02:18 +02:00
"avatars" ,
"commits" ,
"debug" ,
"error" ,
"explore" ,
2019-02-28 01:04:44 +01:00
"ghost" ,
2018-08-13 07:02:18 +02:00
"help" ,
"install" ,
"issues" ,
"less" ,
2020-04-18 23:01:06 +02:00
"login" ,
2020-02-16 08:56:49 +01:00
"manifest.json" ,
2018-11-05 04:20:00 +01:00
"metrics" ,
2020-02-16 08:56:49 +01:00
"milestones" ,
2018-08-13 07:02:18 +02:00
"new" ,
2019-02-28 01:04:44 +01:00
"notifications" ,
2018-08-13 07:02:18 +02:00
"org" ,
"plugins" ,
"pulls" ,
"raw" ,
"repo" ,
2020-04-18 23:01:06 +02:00
"robots.txt" ,
"search" ,
2018-08-13 07:02:18 +02:00
"stars" ,
"template" ,
"user" ,
2020-06-20 15:20:25 +02:00
} , public . KnownPublicEntries ... )
2019-04-14 18:43:56 +02:00
reservedUserPatterns = [ ] string { "*.keys" , "*.gpg" }
2016-07-23 12:58:18 +02:00
)
// isUsableName checks if name is reserved or pattern of name is not allowed
2016-11-12 13:26:45 +01:00
// based on given reserved names and patterns.
2016-07-23 12:58:18 +02:00
// Names are exact match, patterns can be prefix or suffix match with placeholder '*'.
func isUsableName ( names , patterns [ ] string , name string ) error {
name = strings . TrimSpace ( strings . ToLower ( name ) )
if utf8 . RuneCountInString ( name ) == 0 {
return ErrNameEmpty
}
for i := range names {
if name == names [ i ] {
return ErrNameReserved { name }
}
}
for _ , pat := range patterns {
if pat [ 0 ] == '*' && strings . HasSuffix ( name , pat [ 1 : ] ) ||
( pat [ len ( pat ) - 1 ] == '*' && strings . HasPrefix ( name , pat [ : len ( pat ) - 1 ] ) ) {
return ErrNamePatternNotAllowed { pat }
}
}
return nil
}
2016-11-28 17:47:46 +01:00
// IsUsableUsername returns an error when a username is reserved
2016-07-23 12:58:18 +02:00
func IsUsableUsername ( name string ) error {
2020-02-23 20:52:05 +01:00
// Validate username make sure it satisfies requirement.
if alphaDashDotPattern . MatchString ( name ) {
// Note: usually this error is normally caught up earlier in the UI
return ErrNameCharsNotAllowed { Name : name }
}
2016-11-12 13:26:45 +01:00
return isUsableName ( reservedUsernames , reservedUserPatterns , name )
2016-07-23 12:58:18 +02:00
}
2014-06-25 06:44:48 +02:00
// CreateUser creates record of a new user.
2015-03-26 22:11:47 +01:00
func CreateUser ( u * User ) ( err error ) {
2016-07-23 12:58:18 +02:00
if err = IsUsableUsername ( u . Name ) ; err != nil {
2015-03-26 22:11:47 +01:00
return err
2014-06-25 06:44:48 +02:00
}
2017-10-03 08:29:26 +02:00
sess := x . NewSession ( )
defer sess . Close ( )
if err = sess . Begin ( ) ; err != nil {
return err
}
isExist , err := isUserExist ( sess , 0 , u . Name )
2014-06-25 06:44:48 +02:00
if err != nil {
2014-07-26 06:24:27 +02:00
return err
2014-06-25 06:44:48 +02:00
} else if isExist {
2015-03-26 22:11:47 +01:00
return ErrUserAlreadyExist { u . Name }
2014-06-25 06:44:48 +02:00
}
2015-11-24 02:43:04 +01:00
u . Email = strings . ToLower ( u . Email )
2017-10-03 08:29:26 +02:00
isExist , err = sess .
2017-01-05 01:52:20 +01:00
Where ( "email=?" , u . Email ) .
Get ( new ( User ) )
if err != nil {
return err
2017-10-03 08:29:26 +02:00
} else if isExist {
2017-01-05 01:52:20 +01:00
return ErrEmailAlreadyUsed { u . Email }
}
2020-11-22 18:31:35 +01:00
if err = ValidateEmail ( u . Email ) ; err != nil {
return err
}
2017-10-03 08:29:26 +02:00
isExist , err = isEmailUsed ( sess , u . Email )
2014-06-25 06:44:48 +02:00
if err != nil {
2014-07-26 06:24:27 +02:00
return err
2014-06-25 06:44:48 +02:00
} else if isExist {
2015-03-26 22:11:47 +01:00
return ErrEmailAlreadyUsed { u . Email }
2014-06-25 06:44:48 +02:00
}
2017-01-08 04:12:03 +01:00
u . KeepEmailPrivate = setting . Service . DefaultKeepEmailPrivate
2014-06-25 06:44:48 +02:00
u . LowerName = strings . ToLower ( u . Name )
u . AvatarEmail = u . Email
2016-12-20 13:32:02 +01:00
if u . Rands , err = GetUserSalt ( ) ; err != nil {
return err
}
if u . Salt , err = GetUserSalt ( ) ; err != nil {
return err
}
2018-01-11 23:19:38 +01:00
u . HashPassword ( u . Passwd )
2019-05-24 18:40:45 +02:00
u . AllowCreateOrganization = setting . Service . DefaultAllowCreateOrganization && ! setting . Admin . DisableRegularOrgCreation
2019-08-29 16:05:42 +02:00
u . EmailNotificationsPreference = setting . Admin . DefaultEmailNotification
2015-12-11 21:11:13 +01:00
u . MaxRepoCreation = - 1
2019-01-09 18:22:57 +01:00
u . Theme = setting . UI . DefaultTheme
2014-06-25 06:44:48 +02:00
if _ , err = sess . Insert ( u ) ; err != nil {
2014-07-26 06:24:27 +02:00
return err
2014-06-25 06:44:48 +02:00
}
2014-04-22 18:55:27 +02:00
2015-08-18 22:58:45 +02:00
return sess . Commit ( )
2014-06-25 06:44:48 +02:00
}
2015-08-06 18:00:11 +02:00
func countUsers ( e Engine ) int64 {
2016-11-10 16:16:32 +01:00
count , _ := e .
Where ( "type=0" ) .
Count ( new ( User ) )
2015-08-06 18:00:11 +02:00
return count
}
2014-07-07 10:15:08 +02:00
// CountUsers returns number of users.
func CountUsers ( ) int64 {
2015-08-06 18:00:11 +02:00
return countUsers ( x )
2014-07-07 10:15:08 +02:00
}
2017-01-05 01:50:34 +01:00
// get user by verify code
2014-04-10 20:20:58 +02:00
func getVerifyUser ( code string ) ( user * User ) {
if len ( code ) <= base . TimeLimitCodeLength {
return nil
}
// use tail hex username query user
hexStr := code [ base . TimeLimitCodeLength : ]
if b , err := hex . DecodeString ( hexStr ) ; err == nil {
if user , err = GetUserByName ( string ( b ) ) ; user != nil {
return user
}
2019-04-02 09:48:31 +02:00
log . Error ( "user.getVerifyUser: %v" , err )
2014-04-10 20:20:58 +02:00
}
return nil
}
2016-11-28 17:47:46 +01:00
// VerifyUserActiveCode verifies active code when active account
2014-04-10 20:20:58 +02:00
func VerifyUserActiveCode ( code string ) ( user * User ) {
2014-05-26 02:11:25 +02:00
minutes := setting . Service . ActiveCodeLives
2014-04-10 20:20:58 +02:00
if user = getVerifyUser ( code ) ; user != nil {
// time limit code
prefix := code [ : base . TimeLimitCodeLength ]
2016-07-23 19:08:22 +02:00
data := com . ToStr ( user . ID ) + user . Email + user . LowerName + user . Passwd + user . Rands
2014-04-10 20:20:58 +02:00
if base . VerifyTimeLimitCode ( data , minutes , prefix ) {
return user
}
}
return nil
}
2016-11-28 17:47:46 +01:00
// VerifyActiveEmailCode verifies active email code when active account
2014-12-17 16:40:10 +01:00
func VerifyActiveEmailCode ( code , email string ) * EmailAddress {
minutes := setting . Service . ActiveCodeLives
if user := getVerifyUser ( code ) ; user != nil {
// time limit code
prefix := code [ : base . TimeLimitCodeLength ]
2016-07-23 19:08:22 +02:00
data := com . ToStr ( user . ID ) + email + user . LowerName + user . Passwd + user . Rands
2014-12-17 16:40:10 +01:00
if base . VerifyTimeLimitCode ( data , minutes , prefix ) {
2020-02-21 14:08:04 +01:00
emailAddress := & EmailAddress { UID : user . ID , Email : email }
2014-12-17 16:40:10 +01:00
if has , _ := x . Get ( emailAddress ) ; has {
return emailAddress
}
}
}
return nil
}
2014-04-10 20:20:58 +02:00
// ChangeUserName changes all corresponding setting from old user name to new one.
2014-07-26 06:24:27 +02:00
func ChangeUserName ( u * User , newUserName string ) ( err error ) {
2021-01-25 00:36:16 +01:00
oldUserName := u . Name
2016-07-23 12:58:18 +02:00
if err = IsUsableUsername ( newUserName ) ; err != nil {
2015-03-26 22:11:47 +01:00
return err
}
isExist , err := IsUserExist ( 0 , newUserName )
if err != nil {
return err
} else if isExist {
return ErrUserAlreadyExist { newUserName }
2014-07-26 06:24:27 +02:00
}
2020-02-09 15:33:03 +01:00
2020-02-08 15:59:40 +01:00
sess := x . NewSession ( )
defer sess . Close ( )
if err = sess . Begin ( ) ; err != nil {
return err
}
2021-01-25 00:36:16 +01:00
if _ , err = sess . Exec ( "UPDATE `repository` SET owner_name=? WHERE owner_name=?" , newUserName , oldUserName ) ; err != nil {
2020-02-08 15:59:40 +01:00
return fmt . Errorf ( "Change repo owner name: %v" , err )
}
2016-01-27 22:45:03 +01:00
2018-09-07 02:40:58 +02:00
// Do not fail if directory does not exist
2021-01-25 00:36:16 +01:00
if err = os . Rename ( UserPath ( oldUserName ) , UserPath ( newUserName ) ) ; err != nil && ! os . IsNotExist ( err ) {
2018-09-07 02:40:58 +02:00
return fmt . Errorf ( "Rename user directory: %v" , err )
}
2021-01-25 00:36:16 +01:00
if err = sess . Commit ( ) ; err != nil {
if err2 := os . Rename ( UserPath ( newUserName ) , UserPath ( oldUserName ) ) ; err2 != nil && ! os . IsNotExist ( err2 ) {
log . Critical ( "Unable to rollback directory change during failed username change from: %s to: %s. DB Error: %v. Filesystem Error: %v" , oldUserName , newUserName , err , err2 )
return fmt . Errorf ( "failed to rollback directory change during failed username change from: %s to: %s. DB Error: %w. Filesystem Error: %v" , oldUserName , newUserName , err , err2 )
}
return err
}
return nil
2014-04-10 20:20:58 +02:00
}
2017-02-25 15:53:57 +01:00
// checkDupEmail checks whether there are the same email with the user
func checkDupEmail ( e Engine , u * User ) error {
u . Email = strings . ToLower ( u . Email )
has , err := e .
Where ( "id!=?" , u . ID ) .
And ( "type=?" , u . Type ) .
And ( "email=?" , u . Email ) .
Get ( new ( User ) )
if err != nil {
return err
} else if has {
return ErrEmailAlreadyUsed { u . Email }
}
return nil
}
2020-11-22 18:31:35 +01:00
func updateUser ( e Engine , u * User ) ( err error ) {
u . Email = strings . ToLower ( u . Email )
if err = ValidateEmail ( u . Email ) ; err != nil {
return err
}
_ , err = e . ID ( u . ID ) . AllCols ( ) . Update ( u )
2014-04-10 20:20:58 +02:00
return err
}
2015-08-29 19:13:24 +02:00
// UpdateUser updates user's information.
func UpdateUser ( u * User ) error {
return updateUser ( x , u )
2017-02-25 15:53:57 +01:00
}
2017-08-12 16:18:44 +02:00
// UpdateUserCols update user according special columns
func UpdateUserCols ( u * User , cols ... string ) error {
2017-09-25 06:59:27 +02:00
return updateUserCols ( x , u , cols ... )
}
func updateUserCols ( e Engine , u * User , cols ... string ) error {
2017-10-05 06:43:04 +02:00
_ , err := e . ID ( u . ID ) . Cols ( cols ... ) . Update ( u )
2017-08-12 16:18:44 +02:00
return err
}
2017-02-25 15:53:57 +01:00
// UpdateUserSetting updates user's settings.
2020-11-22 18:31:35 +01:00
func UpdateUserSetting ( u * User ) ( err error ) {
sess := x . NewSession ( )
defer sess . Close ( )
if err = sess . Begin ( ) ; err != nil {
return err
}
2017-02-25 15:53:57 +01:00
if ! u . IsOrganization ( ) {
2020-11-22 18:31:35 +01:00
if err = checkDupEmail ( sess , u ) ; err != nil {
2017-02-25 15:53:57 +01:00
return err
}
}
2020-11-22 18:31:35 +01:00
if err = updateUser ( sess , u ) ; err != nil {
return err
}
return sess . Commit ( )
2015-08-29 19:13:24 +02:00
}
2015-09-06 14:54:08 +02:00
// deleteBeans deletes all given beans, beans should contain delete conditions.
func deleteBeans ( e Engine , beans ... interface { } ) ( err error ) {
2015-03-18 02:51:39 +01:00
for i := range beans {
if _ , err = e . Delete ( beans [ i ] ) ; err != nil {
return err
}
}
return nil
}
2021-01-23 03:03:29 +01:00
func deleteUser ( e Engine , u * User ) error {
2015-08-17 11:05:37 +02:00
// Note: A user owns any repository or belongs to any organization
// cannot perform delete operation.
2014-04-10 20:20:58 +02:00
// Check ownership of repository.
2015-09-06 14:54:08 +02:00
count , err := getRepositoryCount ( e , u )
2014-04-10 20:20:58 +02:00
if err != nil {
2015-03-18 02:51:39 +01:00
return fmt . Errorf ( "GetRepositoryCount: %v" , err )
2014-04-10 20:20:58 +02:00
} else if count > 0 {
2016-07-23 19:08:22 +02:00
return ErrUserOwnRepos { UID : u . ID }
2014-04-10 20:20:58 +02:00
}
2014-06-27 09:37:01 +02:00
// Check membership of organization.
2015-09-06 14:54:08 +02:00
count , err = u . getOrganizationCount ( e )
2014-06-27 09:37:01 +02:00
if err != nil {
2015-03-18 02:51:39 +01:00
return fmt . Errorf ( "GetOrganizationCount: %v" , err )
2014-06-27 09:37:01 +02:00
} else if count > 0 {
2016-07-23 19:08:22 +02:00
return ErrUserHasOrgs { UID : u . ID }
2014-06-27 09:37:01 +02:00
}
2015-08-17 11:05:37 +02:00
// ***** START: Watch *****
2017-05-20 10:48:22 +02:00
watchedRepoIDs := make ( [ ] int64 , 0 , 10 )
if err = e . Table ( "watch" ) . Cols ( "watch.repo_id" ) .
2019-11-10 10:22:19 +01:00
Where ( "watch.user_id = ?" , u . ID ) . And ( "watch.mode <>?" , RepoWatchModeDont ) . Find ( & watchedRepoIDs ) ; err != nil {
2015-03-18 02:51:39 +01:00
return fmt . Errorf ( "get all watches: %v" , err )
2014-04-10 20:20:58 +02:00
}
2018-07-04 23:47:05 +02:00
if _ , err = e . Decr ( "num_watches" ) . In ( "id" , watchedRepoIDs ) . NoAutoTime ( ) . Update ( new ( Repository ) ) ; err != nil {
2017-05-20 10:48:22 +02:00
return fmt . Errorf ( "decrease repository num_watches: %v" , err )
2014-04-12 03:47:39 +02:00
}
2015-08-17 11:05:37 +02:00
// ***** END: Watch *****
2015-03-18 02:51:39 +01:00
2015-08-17 11:05:37 +02:00
// ***** START: Star *****
2017-05-20 10:48:22 +02:00
starredRepoIDs := make ( [ ] int64 , 0 , 10 )
if err = e . Table ( "star" ) . Cols ( "star.repo_id" ) .
Where ( "star.uid = ?" , u . ID ) . Find ( & starredRepoIDs ) ; err != nil {
2015-08-17 11:05:37 +02:00
return fmt . Errorf ( "get all stars: %v" , err )
2018-07-04 23:47:05 +02:00
} else if _ , err = e . Decr ( "num_stars" ) . In ( "id" , starredRepoIDs ) . NoAutoTime ( ) . Update ( new ( Repository ) ) ; err != nil {
2017-05-20 10:48:22 +02:00
return fmt . Errorf ( "decrease repository num_stars: %v" , err )
2015-08-17 11:05:37 +02:00
}
// ***** END: Star *****
2015-03-18 02:51:39 +01:00
2015-08-17 11:05:37 +02:00
// ***** START: Follow *****
2017-05-20 10:48:22 +02:00
followeeIDs := make ( [ ] int64 , 0 , 10 )
if err = e . Table ( "follow" ) . Cols ( "follow.follow_id" ) .
Where ( "follow.user_id = ?" , u . ID ) . Find ( & followeeIDs ) ; err != nil {
return fmt . Errorf ( "get all followees: %v" , err )
} else if _ , err = e . Decr ( "num_followers" ) . In ( "id" , followeeIDs ) . Update ( new ( User ) ) ; err != nil {
return fmt . Errorf ( "decrease user num_followers: %v" , err )
2015-08-17 11:05:37 +02:00
}
2017-05-20 10:48:22 +02:00
followerIDs := make ( [ ] int64 , 0 , 10 )
if err = e . Table ( "follow" ) . Cols ( "follow.user_id" ) .
Where ( "follow.follow_id = ?" , u . ID ) . Find ( & followerIDs ) ; err != nil {
return fmt . Errorf ( "get all followers: %v" , err )
} else if _ , err = e . Decr ( "num_following" ) . In ( "id" , followerIDs ) . Update ( new ( User ) ) ; err != nil {
return fmt . Errorf ( "decrease user num_following: %v" , err )
2014-04-10 20:20:58 +02:00
}
2015-08-17 11:05:37 +02:00
// ***** END: Follow *****
2015-03-18 02:51:39 +01:00
2015-09-06 14:54:08 +02:00
if err = deleteBeans ( e ,
2016-07-23 19:08:22 +02:00
& AccessToken { UID : u . ID } ,
& Collaboration { UserID : u . ID } ,
& Access { UserID : u . ID } ,
& Watch { UserID : u . ID } ,
& Star { UID : u . ID } ,
2017-05-20 10:48:22 +02:00
& Follow { UserID : u . ID } ,
2016-07-23 19:08:22 +02:00
& Follow { FollowID : u . ID } ,
& Action { UserID : u . ID } ,
& IssueUser { UID : u . ID } ,
& EmailAddress { UID : u . ID } ,
2017-03-17 15:16:08 +01:00
& UserOpenID { UID : u . ID } ,
2017-12-04 00:14:26 +01:00
& Reaction { UserID : u . ID } ,
2018-12-18 17:26:26 +01:00
& TeamUser { UID : u . ID } ,
& Collaboration { UserID : u . ID } ,
& Stopwatch { UserID : u . ID } ,
2015-03-18 02:51:39 +01:00
) ; err != nil {
2015-12-01 02:45:55 +01:00
return fmt . Errorf ( "deleteBeans: %v" , err )
2014-04-10 20:20:58 +02:00
}
2015-03-18 02:51:39 +01:00
2015-08-17 11:05:37 +02:00
// ***** START: PublicKey *****
2018-12-18 17:26:26 +01:00
if _ , err = e . Delete ( & PublicKey { OwnerID : u . ID } ) ; err != nil {
2016-07-26 11:26:48 +02:00
return fmt . Errorf ( "deletePublicKeys: %v" , err )
2014-04-10 20:20:58 +02:00
}
2019-06-12 21:41:28 +02:00
err = rewriteAllPublicKeys ( e )
if err != nil {
return err
}
2020-10-11 02:38:09 +02:00
err = rewriteAllPrincipalKeys ( e )
if err != nil {
return err
}
2015-08-17 11:05:37 +02:00
// ***** END: PublicKey *****
2014-04-10 20:20:58 +02:00
2018-12-18 17:26:26 +01:00
// ***** START: GPGPublicKey *****
if _ , err = e . Delete ( & GPGKey { OwnerID : u . ID } ) ; err != nil {
return fmt . Errorf ( "deleteGPGKeys: %v" , err )
}
// ***** END: GPGPublicKey *****
2015-08-14 20:48:05 +02:00
// Clear assignee.
2018-05-09 18:29:04 +02:00
if err = clearAssigneeByUserID ( e , u . ID ) ; err != nil {
2015-08-17 11:05:37 +02:00
return fmt . Errorf ( "clear assignee: %v" , err )
2015-08-14 20:48:05 +02:00
}
2017-02-22 08:14:37 +01:00
// ***** START: ExternalLoginUser *****
2017-03-20 15:13:52 +01:00
if err = removeAllAccountLinks ( e , u ) ; err != nil {
2017-02-22 08:14:37 +01:00
return fmt . Errorf ( "ExternalLoginUser: %v" , err )
}
// ***** END: ExternalLoginUser *****
2017-10-05 06:43:04 +02:00
if _ , err = e . ID ( u . ID ) . Delete ( new ( User ) ) ; err != nil {
2015-08-17 11:05:37 +02:00
return fmt . Errorf ( "Delete: %v" , err )
2015-03-18 02:51:39 +01:00
}
2015-08-17 11:05:37 +02:00
// Note: There are something just cannot be roll back,
// so just keep error logs of those operations.
2016-12-01 00:56:15 +01:00
path := UserPath ( u . Name )
2021-01-23 03:03:29 +01:00
if err = util . RemoveAll ( path ) ; err != nil {
err = fmt . Errorf ( "Failed to RemoveAll %s: %v" , path , err )
_ = createNotice ( e , NoticeTask , fmt . Sprintf ( "delete user '%s': %v" , u . Name , err ) )
return err
2016-12-01 00:56:15 +01:00
}
2017-03-06 09:15:40 +01:00
if len ( u . Avatar ) > 0 {
2020-10-14 15:07:51 +02:00
avatarPath := u . CustomAvatarRelativePath ( )
2021-01-23 03:03:29 +01:00
if err = storage . Avatars . Delete ( avatarPath ) ; err != nil {
err = fmt . Errorf ( "Failed to remove %s: %v" , avatarPath , err )
_ = createNotice ( e , NoticeTask , fmt . Sprintf ( "delete user '%s': %v" , u . Name , err ) )
return err
2016-12-27 03:02:14 +01:00
}
2016-12-01 00:56:15 +01:00
}
2014-04-10 20:20:58 +02:00
2015-08-17 11:05:37 +02:00
return nil
2014-06-21 06:51:41 +02:00
}
2015-09-06 14:54:08 +02:00
// DeleteUser completely and permanently deletes everything of a user,
// but issues/comments/pulls will be kept and shown as someone has been deleted.
func DeleteUser ( u * User ) ( err error ) {
2020-02-04 15:27:18 +01:00
if u . IsOrganization ( ) {
return fmt . Errorf ( "%s is an organization not a user" , u . Name )
}
2015-09-06 14:54:08 +02:00
sess := x . NewSession ( )
2017-06-21 02:57:05 +02:00
defer sess . Close ( )
2015-09-06 14:54:08 +02:00
if err = sess . Begin ( ) ; err != nil {
return err
}
if err = deleteUser ( sess , u ) ; err != nil {
2015-09-13 19:26:20 +02:00
// Note: don't wrapper error here.
return err
2015-09-06 14:54:08 +02:00
}
2018-12-18 17:26:26 +01:00
return sess . Commit ( )
2015-09-06 14:54:08 +02:00
}
2020-05-17 01:31:38 +02:00
// DeleteInactiveUsers deletes all inactive users and email addresses.
func DeleteInactiveUsers ( ctx context . Context , olderThan time . Duration ) ( err error ) {
2015-08-17 11:05:37 +02:00
users := make ( [ ] * User , 0 , 10 )
2020-05-17 01:31:38 +02:00
if olderThan > 0 {
if err = x .
Where ( "is_active = ? and created_unix < ?" , false , time . Now ( ) . Add ( - olderThan ) . Unix ( ) ) .
Find ( & users ) ; err != nil {
return fmt . Errorf ( "get all inactive users: %v" , err )
}
} else {
if err = x .
Where ( "is_active = ?" , false ) .
Find ( & users ) ; err != nil {
return fmt . Errorf ( "get all inactive users: %v" , err )
}
2015-08-17 11:05:37 +02:00
}
2016-07-26 11:26:48 +02:00
// FIXME: should only update authorized_keys file once after all deletions.
2015-08-17 11:05:37 +02:00
for _ , u := range users {
2020-05-17 01:31:38 +02:00
select {
case <- ctx . Done ( ) :
return ErrCancelledf ( "Before delete inactive user %s" , u . Name )
default :
}
2015-08-17 11:05:37 +02:00
if err = DeleteUser ( u ) ; err != nil {
// Ignore users that were set inactive by admin.
if IsErrUserOwnRepos ( err ) || IsErrUserHasOrgs ( err ) {
continue
}
return err
}
2014-12-17 09:26:19 +01:00
}
2015-08-17 11:05:37 +02:00
2016-11-10 16:16:32 +01:00
_ , err = x .
Where ( "is_activated = ?" , false ) .
Delete ( new ( EmailAddress ) )
2014-04-10 20:20:58 +02:00
return err
}
// UserPath returns the path absolute path of user repositories.
func UserPath ( userName string ) string {
2014-05-26 02:11:25 +02:00
return filepath . Join ( setting . RepoRootPath , strings . ToLower ( userName ) )
2014-04-10 20:20:58 +02:00
}
2015-08-08 16:43:14 +02:00
func getUserByID ( e Engine , id int64 ) ( * User , error ) {
2014-06-06 04:07:35 +02:00
u := new ( User )
2017-10-05 06:43:04 +02:00
has , err := e . ID ( id ) . Get ( u )
2014-04-10 20:20:58 +02:00
if err != nil {
return nil , err
2014-06-06 04:07:35 +02:00
} else if ! has {
2016-11-12 00:01:09 +01:00
return nil , ErrUserNotExist { id , "" , 0 }
2014-04-10 20:20:58 +02:00
}
2014-06-06 04:07:35 +02:00
return u , nil
2014-04-10 20:20:58 +02:00
}
2015-08-08 16:43:14 +02:00
// GetUserByID returns the user object by given ID if exists.
func GetUserByID ( id int64 ) ( * User , error ) {
return getUserByID ( x , id )
2015-02-13 06:58:46 +01:00
}
2014-10-25 00:43:17 +02:00
// GetUserByName returns user by given name.
2014-04-10 20:20:58 +02:00
func GetUserByName ( name string ) ( * User , error ) {
2017-08-30 06:31:33 +02:00
return getUserByName ( x , name )
}
func getUserByName ( e Engine , name string ) ( * User , error ) {
2014-04-10 20:20:58 +02:00
if len ( name ) == 0 {
2016-11-12 00:01:09 +01:00
return nil , ErrUserNotExist { 0 , name , 0 }
2014-04-10 20:20:58 +02:00
}
2014-10-25 00:43:17 +02:00
u := & User { LowerName : strings . ToLower ( name ) }
2017-08-30 06:31:33 +02:00
has , err := e . Get ( u )
2014-04-10 20:20:58 +02:00
if err != nil {
return nil , err
} else if ! has {
2016-11-12 00:01:09 +01:00
return nil , ErrUserNotExist { 0 , name , 0 }
2014-04-10 20:20:58 +02:00
}
2014-10-25 00:43:17 +02:00
return u , nil
2014-04-10 20:20:58 +02:00
}
2019-08-29 16:05:42 +02:00
// GetUserEmailsByNames returns a list of e-mails corresponds to names of users
// that have their email notifications set to enabled or onmention.
2014-04-10 20:20:58 +02:00
func GetUserEmailsByNames ( names [ ] string ) [ ] string {
2017-08-30 06:31:33 +02:00
return getUserEmailsByNames ( x , names )
}
func getUserEmailsByNames ( e Engine , names [ ] string ) [ ] string {
2014-04-10 20:20:58 +02:00
mails := make ( [ ] string , 0 , len ( names ) )
for _ , name := range names {
2017-08-30 06:31:33 +02:00
u , err := getUserByName ( e , name )
2014-04-10 20:20:58 +02:00
if err != nil {
continue
}
2019-08-29 16:05:42 +02:00
if u . IsMailable ( ) && u . EmailNotifications ( ) != EmailNotificationsDisabled {
2017-02-02 13:33:36 +01:00
mails = append ( mails , u . Email )
}
2014-04-10 20:20:58 +02:00
}
return mails
}
2019-11-18 09:08:20 +01:00
// GetMaileableUsersByIDs gets users from ids, but only if they can receive mails
2020-09-09 21:08:55 +02:00
func GetMaileableUsersByIDs ( ids [ ] int64 , isMention bool ) ( [ ] * User , error ) {
2019-11-18 09:08:20 +01:00
if len ( ids ) == 0 {
return nil , nil
}
ous := make ( [ ] * User , 0 , len ( ids ) )
2020-09-09 21:08:55 +02:00
if isMention {
return ous , x . In ( "id" , ids ) .
Where ( "`type` = ?" , UserTypeIndividual ) .
And ( "`prohibit_login` = ?" , false ) .
And ( "`is_active` = ?" , true ) .
And ( "`email_notifications_preference` IN ( ?, ?)" , EmailNotificationsEnabled , EmailNotificationsOnMention ) .
Find ( & ous )
}
2019-11-18 09:08:20 +01:00
return ous , x . In ( "id" , ids ) .
Where ( "`type` = ?" , UserTypeIndividual ) .
And ( "`prohibit_login` = ?" , false ) .
And ( "`is_active` = ?" , true ) .
And ( "`email_notifications_preference` = ?" , EmailNotificationsEnabled ) .
Find ( & ous )
}
2020-02-13 00:19:35 +01:00
// GetUserNamesByIDs returns usernames for all resolved users from a list of Ids.
func GetUserNamesByIDs ( ids [ ] int64 ) ( [ ] string , error ) {
unames := make ( [ ] string , 0 , len ( ids ) )
err := x . In ( "id" , ids ) .
Table ( "user" ) .
Asc ( "name" ) .
Cols ( "name" ) .
Find ( & unames )
return unames , err
}
2016-11-10 09:10:35 +01:00
// GetUsersByIDs returns all resolved users from a list of Ids.
2020-02-26 07:32:22 +01:00
func GetUsersByIDs ( ids [ ] int64 ) ( UserList , error ) {
2016-11-10 09:10:35 +01:00
ous := make ( [ ] * User , 0 , len ( ids ) )
2017-01-20 03:31:46 +01:00
if len ( ids ) == 0 {
return ous , nil
}
err := x . In ( "id" , ids ) .
2016-11-10 16:16:32 +01:00
Asc ( "name" ) .
Find ( & ous )
2016-11-10 09:10:35 +01:00
return ous , err
}
2016-07-16 04:08:04 +02:00
// GetUserIDsByNames returns a slice of ids corresponds to names.
2019-10-25 16:46:37 +02:00
func GetUserIDsByNames ( names [ ] string , ignoreNonExistent bool ) ( [ ] int64 , error ) {
2014-05-07 22:51:14 +02:00
ids := make ( [ ] int64 , 0 , len ( names ) )
for _ , name := range names {
u , err := GetUserByName ( name )
if err != nil {
2019-10-25 16:46:37 +02:00
if ignoreNonExistent {
continue
} else {
return nil , err
}
2014-05-07 22:51:14 +02:00
}
2016-07-23 19:08:22 +02:00
ids = append ( ids , u . ID )
2014-05-07 22:51:14 +02:00
}
2019-10-25 16:46:37 +02:00
return ids , nil
2014-05-07 22:51:14 +02:00
}
2014-12-07 02:22:48 +01:00
// UserCommit represents a commit with validation of user.
2014-09-23 21:30:04 +02:00
type UserCommit struct {
2014-11-21 16:58:08 +01:00
User * User
2015-12-10 02:46:05 +01:00
* git . Commit
2014-09-23 21:30:04 +02:00
}
2017-01-05 01:50:34 +01:00
// ValidateCommitWithEmail check if author's e-mail of commit is corresponding to a user.
2015-12-10 02:46:05 +01:00
func ValidateCommitWithEmail ( c * git . Commit ) * User {
2017-10-26 09:45:14 +02:00
if c . Author == nil {
return nil
}
2014-09-26 14:55:13 +02:00
u , err := GetUserByEmail ( c . Author . Email )
2014-11-21 16:58:08 +01:00
if err != nil {
return nil
2014-09-26 14:55:13 +02:00
}
2014-11-21 16:58:08 +01:00
return u
2014-09-26 14:55:13 +02:00
}
// ValidateCommitsWithEmails checks if authors' e-mails of commits are corresponding to users.
func ValidateCommitsWithEmails ( oldCommits * list . List ) * list . List {
2015-08-05 11:36:22 +02:00
var (
u * User
emails = map [ string ] * User { }
newCommits = list . New ( )
e = oldCommits . Front ( )
)
2014-09-23 21:30:04 +02:00
for e != nil {
2015-12-10 02:46:05 +01:00
c := e . Value . ( * git . Commit )
2014-09-23 21:30:04 +02:00
2017-10-26 09:45:14 +02:00
if c . Author != nil {
if v , ok := emails [ c . Author . Email ] ; ! ok {
u , _ = GetUserByEmail ( c . Author . Email )
emails [ c . Author . Email ] = u
} else {
u = v
}
2014-09-24 05:18:14 +02:00
} else {
2017-10-26 09:45:14 +02:00
u = nil
2014-09-23 21:30:04 +02:00
}
newCommits . PushBack ( UserCommit {
2014-11-21 16:58:08 +01:00
User : u ,
Commit : c ,
2014-09-23 21:30:04 +02:00
} )
e = e . Next ( )
}
return newCommits
}
2014-04-10 20:20:58 +02:00
// GetUserByEmail returns the user object by given e-mail if exists.
func GetUserByEmail ( email string ) ( * User , error ) {
2020-02-03 09:47:04 +01:00
return GetUserByEmailContext ( DefaultDBContext ( ) , email )
}
// GetUserByEmailContext returns the user object by given e-mail if exists with db context
func GetUserByEmailContext ( ctx DBContext , email string ) ( * User , error ) {
2014-04-10 20:20:58 +02:00
if len ( email ) == 0 {
2016-11-12 00:01:09 +01:00
return nil , ErrUserNotExist { 0 , email , 0 }
2014-04-10 20:20:58 +02:00
}
2015-08-05 11:36:22 +02:00
email = strings . ToLower ( email )
2014-12-17 09:26:19 +01:00
// First try to find the user by primary email
2015-08-05 11:36:22 +02:00
user := & User { Email : email }
2020-02-03 09:47:04 +01:00
has , err := ctx . e . Get ( user )
2014-04-10 20:20:58 +02:00
if err != nil {
return nil , err
}
2014-12-17 09:26:19 +01:00
if has {
return user , nil
}
// Otherwise, check in alternative list for activated email addresses
2015-08-05 11:36:22 +02:00
emailAddress := & EmailAddress { Email : email , IsActivated : true }
2020-02-03 09:47:04 +01:00
has , err = ctx . e . Get ( emailAddress )
2014-12-17 09:26:19 +01:00
if err != nil {
return nil , err
}
if has {
2020-02-03 09:47:04 +01:00
return getUserByID ( ctx . e , emailAddress . UID )
2014-12-17 09:26:19 +01:00
}
2019-06-06 07:54:25 +02:00
// Finally, if email address is the protected email address:
if strings . HasSuffix ( email , fmt . Sprintf ( "@%s" , setting . Service . NoReplyAddress ) ) {
username := strings . TrimSuffix ( email , fmt . Sprintf ( "@%s" , setting . Service . NoReplyAddress ) )
2020-06-17 19:50:11 +02:00
user := & User { }
has , err := ctx . e . Where ( "lower_name=?" , username ) . Get ( user )
2019-06-06 07:54:25 +02:00
if err != nil {
return nil , err
}
if has {
return user , nil
}
}
2016-11-12 00:01:09 +01:00
return nil , ErrUserNotExist { 0 , email , 0 }
2014-04-10 20:20:58 +02:00
}
2017-02-22 08:14:37 +01:00
// GetUser checks if a user already exists
func GetUser ( user * User ) ( bool , error ) {
return x . Get ( user )
}
2016-11-28 17:47:46 +01:00
// SearchUserOptions contains the options for searching
2016-03-11 21:33:12 +01:00
type SearchUserOptions struct {
2020-01-24 20:00:29 +01:00
ListOptions
2017-11-26 09:40:38 +01:00
Keyword string
Type UserType
2018-10-18 10:44:51 +02:00
UID int64
2018-05-24 03:03:42 +02:00
OrderBy SearchOrderBy
2020-01-12 16:43:44 +01:00
Visible [ ] structs . VisibleType
2020-01-13 18:33:46 +01:00
Actor * User // The user doing the search
2017-11-26 09:40:38 +01:00
IsActive util . OptionalBool
SearchByEmail bool // Search by email as well as username/full name
2016-03-11 21:33:12 +01:00
}
2017-10-24 19:36:19 +02:00
func ( opts * SearchUserOptions ) toConds ( ) builder . Cond {
2019-08-04 20:33:36 +02:00
var cond builder . Cond = builder . Eq { "type" : opts . Type }
2019-02-18 17:00:27 +01:00
2017-10-24 19:36:19 +02:00
if len ( opts . Keyword ) > 0 {
lowerKeyword := strings . ToLower ( opts . Keyword )
2017-11-26 09:40:38 +01:00
keywordCond := builder . Or (
2017-10-24 19:36:19 +02:00
builder . Like { "lower_name" , lowerKeyword } ,
builder . Like { "LOWER(full_name)" , lowerKeyword } ,
2017-11-26 09:40:38 +01:00
)
if opts . SearchByEmail {
keywordCond = keywordCond . Or ( builder . Like { "LOWER(email)" , lowerKeyword } )
}
2018-10-18 10:44:51 +02:00
2017-11-26 09:40:38 +01:00
cond = cond . And ( keywordCond )
2016-03-11 21:33:12 +01:00
}
2020-01-12 16:43:44 +01:00
if len ( opts . Visible ) > 0 {
cond = cond . And ( builder . In ( "visibility" , opts . Visible ) )
} else {
2019-02-18 17:00:27 +01:00
cond = cond . And ( builder . In ( "visibility" , structs . VisibleTypePublic ) )
}
2020-01-13 18:33:46 +01:00
if opts . Actor != nil {
2019-02-18 17:00:27 +01:00
var exprCond builder . Cond
2019-08-24 11:24:45 +02:00
if setting . Database . UseMySQL {
2019-02-18 17:00:27 +01:00
exprCond = builder . Expr ( "org_user.org_id = user.id" )
2019-08-24 11:24:45 +02:00
} else if setting . Database . UseMSSQL {
2019-02-18 17:00:27 +01:00
exprCond = builder . Expr ( "org_user.org_id = [user].id" )
} else {
exprCond = builder . Expr ( "org_user.org_id = \"user\".id" )
}
2020-01-13 18:33:46 +01:00
var accessCond = builder . NewCond ( )
if ! opts . Actor . IsRestricted {
accessCond = builder . Or (
builder . In ( "id" , builder . Select ( "org_id" ) . From ( "org_user" ) . LeftJoin ( "`user`" , exprCond ) . Where ( builder . And ( builder . Eq { "uid" : opts . Actor . ID } , builder . Eq { "visibility" : structs . VisibleTypePrivate } ) ) ) ,
builder . In ( "visibility" , structs . VisibleTypePublic , structs . VisibleTypeLimited ) )
} else {
// restricted users only see orgs they are a member of
accessCond = builder . In ( "id" , builder . Select ( "org_id" ) . From ( "org_user" ) . LeftJoin ( "`user`" , exprCond ) . Where ( builder . And ( builder . Eq { "uid" : opts . Actor . ID } ) ) )
}
2019-02-18 17:00:27 +01:00
cond = cond . And ( accessCond )
}
2018-10-18 10:44:51 +02:00
if opts . UID > 0 {
cond = cond . And ( builder . Eq { "id" : opts . UID } )
}
2017-10-24 19:36:19 +02:00
if ! opts . IsActive . IsNone ( ) {
cond = cond . And ( builder . Eq { "is_active" : opts . IsActive . IsTrue ( ) } )
2016-03-11 21:33:12 +01:00
}
2017-10-24 19:36:19 +02:00
return cond
}
2016-03-11 21:33:12 +01:00
2017-10-24 19:36:19 +02:00
// SearchUsers takes options i.e. keyword and part of user name to search,
// it returns results in given range and number of total results.
func SearchUsers ( opts * SearchUserOptions ) ( users [ ] * User , _ int64 , _ error ) {
cond := opts . toConds ( )
2017-02-25 14:42:20 +01:00
count , err := x . Where ( cond ) . Count ( new ( User ) )
2016-03-11 21:33:12 +01:00
if err != nil {
return nil , 0 , fmt . Errorf ( "Count: %v" , err )
2014-05-01 05:48:01 +02:00
}
2017-10-24 19:36:19 +02:00
if len ( opts . OrderBy ) == 0 {
2018-10-20 04:25:38 +02:00
opts . OrderBy = SearchOrderByAlphabetically
2017-10-24 19:36:19 +02:00
}
2020-01-24 20:00:29 +01:00
sess := x . Where ( cond ) . OrderBy ( opts . OrderBy . String ( ) )
if opts . Page != 0 {
sess = opts . setSessionPagination ( sess )
2019-02-21 21:42:54 +01:00
}
2019-01-23 23:30:19 +01:00
2017-10-24 19:36:19 +02:00
users = make ( [ ] * User , 0 , opts . PageSize )
2020-01-24 20:00:29 +01:00
return users , count , sess . Find ( & users )
2014-05-01 05:48:01 +02:00
}
2016-11-14 23:33:58 +01:00
// GetStarredRepos returns the repos starred by a particular user
2020-01-24 20:00:29 +01:00
func GetStarredRepos ( userID int64 , private bool , listOptions ListOptions ) ( [ ] * Repository , error ) {
2016-11-14 23:33:58 +01:00
sess := x . Where ( "star.uid=?" , userID ) .
Join ( "LEFT" , "star" , "`repository`.id=`star`.repo_id" )
if ! private {
sess = sess . And ( "is_private=?" , false )
}
2020-01-24 20:00:29 +01:00
if listOptions . Page != 0 {
sess = listOptions . setSessionPagination ( sess )
repos := make ( [ ] * Repository , 0 , listOptions . PageSize )
return repos , sess . Find ( & repos )
2016-11-14 23:33:58 +01:00
}
2020-01-24 20:00:29 +01:00
repos := make ( [ ] * Repository , 0 , 10 )
return repos , sess . Find ( & repos )
2016-11-14 23:33:58 +01:00
}
2016-12-24 02:53:11 +01:00
// GetWatchedRepos returns the repos watched by a particular user
2020-01-24 20:00:29 +01:00
func GetWatchedRepos ( userID int64 , private bool , listOptions ListOptions ) ( [ ] * Repository , error ) {
2016-12-24 02:53:11 +01:00
sess := x . Where ( "watch.user_id=?" , userID ) .
2019-11-10 10:22:19 +01:00
And ( "`watch`.mode<>?" , RepoWatchModeDont ) .
2016-12-24 02:53:11 +01:00
Join ( "LEFT" , "watch" , "`repository`.id=`watch`.repo_id" )
if ! private {
sess = sess . And ( "is_private=?" , false )
}
2020-01-24 20:00:29 +01:00
if listOptions . Page != 0 {
sess = listOptions . setSessionPagination ( sess )
repos := make ( [ ] * Repository , 0 , listOptions . PageSize )
return repos , sess . Find ( & repos )
2016-12-24 02:53:11 +01:00
}
2020-01-24 20:00:29 +01:00
repos := make ( [ ] * Repository , 0 , 10 )
return repos , sess . Find ( & repos )
2016-12-24 02:53:11 +01:00
}
2017-05-10 15:10:18 +02:00
2018-05-24 06:59:02 +02:00
// deleteKeysMarkedForDeletion returns true if ssh keys needs update
func deleteKeysMarkedForDeletion ( keys [ ] string ) ( bool , error ) {
// Start session
sess := x . NewSession ( )
defer sess . Close ( )
if err := sess . Begin ( ) ; err != nil {
return false , err
}
// Delete keys marked for deletion
var sshKeysNeedUpdate bool
for _ , KeyToDelete := range keys {
2018-12-27 18:28:48 +01:00
key , err := searchPublicKeyByContentWithEngine ( sess , KeyToDelete )
2018-05-24 06:59:02 +02:00
if err != nil {
2019-04-02 09:48:31 +02:00
log . Error ( "SearchPublicKeyByContent: %v" , err )
2018-05-24 06:59:02 +02:00
continue
}
if err = deletePublicKeys ( sess , key . ID ) ; err != nil {
2019-04-02 09:48:31 +02:00
log . Error ( "deletePublicKeys: %v" , err )
2018-05-24 06:59:02 +02:00
continue
}
sshKeysNeedUpdate = true
}
if err := sess . Commit ( ) ; err != nil {
return false , err
}
return sshKeysNeedUpdate , nil
}
2018-12-27 18:28:48 +01:00
// addLdapSSHPublicKeys add a users public keys. Returns true if there are changes.
2019-06-12 21:41:28 +02:00
func addLdapSSHPublicKeys ( usr * User , s * LoginSource , sshPublicKeys [ ] string ) bool {
2018-05-24 06:59:02 +02:00
var sshKeysNeedUpdate bool
2019-06-12 21:41:28 +02:00
for _ , sshKey := range sshPublicKeys {
2018-10-31 01:08:30 +01:00
_ , _ , _ , _ , err := ssh . ParseAuthorizedKey ( [ ] byte ( sshKey ) )
if err == nil {
2018-05-24 06:59:02 +02:00
sshKeyName := fmt . Sprintf ( "%s-%s" , s . Name , sshKey [ 0 : 40 ] )
if _ , err := AddPublicKey ( usr . ID , sshKeyName , sshKey , s . ID ) ; err != nil {
2019-04-26 17:01:54 +02:00
if IsErrKeyAlreadyExist ( err ) {
log . Trace ( "addLdapSSHPublicKeys[%s]: LDAP Public SSH Key %s already exists for user" , s . Name , usr . Name )
} else {
log . Error ( "addLdapSSHPublicKeys[%s]: Error adding LDAP Public SSH Key for user %s: %v" , s . Name , usr . Name , err )
}
2018-05-24 06:59:02 +02:00
} else {
log . Trace ( "addLdapSSHPublicKeys[%s]: Added LDAP Public SSH Key for user %s" , s . Name , usr . Name )
sshKeysNeedUpdate = true
}
} else {
log . Warn ( "addLdapSSHPublicKeys[%s]: Skipping invalid LDAP Public SSH Key for user %s: %v" , s . Name , usr . Name , sshKey )
}
}
return sshKeysNeedUpdate
}
2018-12-27 18:28:48 +01:00
// synchronizeLdapSSHPublicKeys updates a users public keys. Returns true if there are changes.
2019-06-12 21:41:28 +02:00
func synchronizeLdapSSHPublicKeys ( usr * User , s * LoginSource , sshPublicKeys [ ] string ) bool {
2018-05-24 06:59:02 +02:00
var sshKeysNeedUpdate bool
log . Trace ( "synchronizeLdapSSHPublicKeys[%s]: Handling LDAP Public SSH Key synchronization for user %s" , s . Name , usr . Name )
// Get Public Keys from DB with current LDAP source
var giteaKeys [ ] string
keys , err := ListPublicLdapSSHKeys ( usr . ID , s . ID )
if err != nil {
2019-04-02 09:48:31 +02:00
log . Error ( "synchronizeLdapSSHPublicKeys[%s]: Error listing LDAP Public SSH Keys for user %s: %v" , s . Name , usr . Name , err )
2018-05-24 06:59:02 +02:00
}
for _ , v := range keys {
giteaKeys = append ( giteaKeys , v . OmitEmail ( ) )
}
// Get Public Keys from LDAP and skip duplicate keys
var ldapKeys [ ] string
2019-06-12 21:41:28 +02:00
for _ , v := range sshPublicKeys {
2019-02-07 03:38:28 +01:00
sshKeySplit := strings . Split ( v , " " )
if len ( sshKeySplit ) > 1 {
ldapKey := strings . Join ( sshKeySplit [ : 2 ] , " " )
if ! util . ExistsInSlice ( ldapKey , ldapKeys ) {
ldapKeys = append ( ldapKeys , ldapKey )
}
2018-05-24 06:59:02 +02:00
}
}
// Check if Public Key sync is needed
if util . IsEqualSlice ( giteaKeys , ldapKeys ) {
log . Trace ( "synchronizeLdapSSHPublicKeys[%s]: LDAP Public Keys are already in sync for %s (LDAP:%v/DB:%v)" , s . Name , usr . Name , len ( ldapKeys ) , len ( giteaKeys ) )
return false
}
log . Trace ( "synchronizeLdapSSHPublicKeys[%s]: LDAP Public Key needs update for user %s (LDAP:%v/DB:%v)" , s . Name , usr . Name , len ( ldapKeys ) , len ( giteaKeys ) )
// Add LDAP Public SSH Keys that doesn't already exist in DB
var newLdapSSHKeys [ ] string
for _ , LDAPPublicSSHKey := range ldapKeys {
if ! util . ExistsInSlice ( LDAPPublicSSHKey , giteaKeys ) {
newLdapSSHKeys = append ( newLdapSSHKeys , LDAPPublicSSHKey )
}
}
2018-12-27 18:28:48 +01:00
if addLdapSSHPublicKeys ( usr , s , newLdapSSHKeys ) {
2018-05-24 06:59:02 +02:00
sshKeysNeedUpdate = true
}
// Mark LDAP keys from DB that doesn't exist in LDAP for deletion
var giteaKeysToDelete [ ] string
for _ , giteaKey := range giteaKeys {
if ! util . ExistsInSlice ( giteaKey , ldapKeys ) {
log . Trace ( "synchronizeLdapSSHPublicKeys[%s]: Marking LDAP Public SSH Key for deletion for user %s: %v" , s . Name , usr . Name , giteaKey )
giteaKeysToDelete = append ( giteaKeysToDelete , giteaKey )
}
}
// Delete LDAP keys from DB that doesn't exist in LDAP
needUpd , err := deleteKeysMarkedForDeletion ( giteaKeysToDelete )
if err != nil {
2019-04-02 09:48:31 +02:00
log . Error ( "synchronizeLdapSSHPublicKeys[%s]: Error deleting LDAP Public SSH Keys marked for deletion for user %s: %v" , s . Name , usr . Name , err )
2018-05-24 06:59:02 +02:00
}
if needUpd {
sshKeysNeedUpdate = true
}
return sshKeysNeedUpdate
}
2017-05-10 15:10:18 +02:00
// SyncExternalUsers is used to synchronize users with external authorization source
2020-05-17 01:31:38 +02:00
func SyncExternalUsers ( ctx context . Context , updateExisting bool ) error {
2017-05-10 15:10:18 +02:00
log . Trace ( "Doing: SyncExternalUsers" )
ls , err := LoginSources ( )
if err != nil {
2019-04-02 09:48:31 +02:00
log . Error ( "SyncExternalUsers: %v" , err )
2020-05-17 01:31:38 +02:00
return err
2017-05-10 15:10:18 +02:00
}
for _ , s := range ls {
if ! s . IsActived || ! s . IsSyncEnabled {
continue
}
2019-12-15 10:51:28 +01:00
select {
case <- ctx . Done ( ) :
2020-05-17 01:31:38 +02:00
log . Warn ( "SyncExternalUsers: Cancelled before update of %s" , s . Name )
return ErrCancelledf ( "Before update of %s" , s . Name )
2019-12-15 10:51:28 +01:00
default :
}
2018-05-24 06:59:02 +02:00
2017-05-10 15:10:18 +02:00
if s . IsLDAP ( ) {
log . Trace ( "Doing: SyncExternalUsers[%s]" , s . Name )
var existingUsers [ ] int64
2018-05-24 06:59:02 +02:00
var isAttributeSSHPublicKeySet = len ( strings . TrimSpace ( s . LDAP ( ) . AttributeSSHPublicKey ) ) > 0
var sshKeysNeedUpdate bool
2017-05-10 15:10:18 +02:00
// Find all users with this login type
2019-05-28 17:45:54 +02:00
var users [ ] * User
2019-06-12 21:41:28 +02:00
err = x . Where ( "login_type = ?" , LoginLDAP ) .
2017-05-10 15:10:18 +02:00
And ( "login_source = ?" , s . ID ) .
Find ( & users )
2019-06-12 21:41:28 +02:00
if err != nil {
log . Error ( "SyncExternalUsers: %v" , err )
2020-05-17 01:31:38 +02:00
return err
2019-06-12 21:41:28 +02:00
}
2019-12-15 10:51:28 +01:00
select {
case <- ctx . Done ( ) :
2020-05-17 01:31:38 +02:00
log . Warn ( "SyncExternalUsers: Cancelled before update of %s" , s . Name )
return ErrCancelledf ( "Before update of %s" , s . Name )
2019-12-15 10:51:28 +01:00
default :
}
2017-05-10 15:10:18 +02:00
2019-08-24 20:53:37 +02:00
sr , err := s . LDAP ( ) . SearchEntries ( )
if err != nil {
log . Error ( "SyncExternalUsers LDAP source failure [%s], skipped" , s . Name )
continue
}
2020-01-20 04:47:39 +01:00
if len ( sr ) == 0 {
if ! s . LDAP ( ) . AllowDeactivateAll {
log . Error ( "LDAP search found no entries but did not report an error. Refusing to deactivate all users" )
continue
} else {
log . Warn ( "LDAP search found no entries but did not report an error. All users will be deactivated as per settings" )
}
}
2017-05-10 15:10:18 +02:00
for _ , su := range sr {
2019-12-15 10:51:28 +01:00
select {
case <- ctx . Done ( ) :
2020-05-17 01:31:38 +02:00
log . Warn ( "SyncExternalUsers: Cancelled at update of %s before completed update of users" , s . Name )
2019-12-15 10:51:28 +01:00
// Rewrite authorized_keys file if LDAP Public SSH Key attribute is set and any key was added or removed
if sshKeysNeedUpdate {
err = RewriteAllPublicKeys ( )
if err != nil {
log . Error ( "RewriteAllPublicKeys: %v" , err )
}
}
2020-05-17 01:31:38 +02:00
return ErrCancelledf ( "During update of %s before completed update of users" , s . Name )
2019-12-15 10:51:28 +01:00
default :
}
2017-05-10 15:10:18 +02:00
if len ( su . Username ) == 0 {
continue
}
if len ( su . Mail ) == 0 {
su . Mail = fmt . Sprintf ( "%s@localhost" , su . Username )
}
var usr * User
// Search for existing user
for _ , du := range users {
if du . LowerName == strings . ToLower ( su . Username ) {
2019-05-28 17:45:54 +02:00
usr = du
2017-05-10 15:10:18 +02:00
break
}
}
fullName := composeFullName ( su . Name , su . Surname , su . Username )
// If no existing user found, create one
if usr == nil {
log . Trace ( "SyncExternalUsers[%s]: Creating user %s" , s . Name , su . Username )
usr = & User {
2020-03-05 07:30:33 +01:00
LowerName : strings . ToLower ( su . Username ) ,
Name : su . Username ,
FullName : fullName ,
LoginType : s . Type ,
LoginSource : s . ID ,
LoginName : su . Username ,
Email : su . Mail ,
IsAdmin : su . IsAdmin ,
IsRestricted : su . IsRestricted ,
IsActive : true ,
2017-05-10 15:10:18 +02:00
}
err = CreateUser ( usr )
2018-05-24 06:59:02 +02:00
2017-05-10 15:10:18 +02:00
if err != nil {
2019-04-02 09:48:31 +02:00
log . Error ( "SyncExternalUsers[%s]: Error creating user %s: %v" , s . Name , su . Username , err )
2018-05-24 06:59:02 +02:00
} else if isAttributeSSHPublicKeySet {
log . Trace ( "SyncExternalUsers[%s]: Adding LDAP Public SSH Keys for user %s" , s . Name , usr . Name )
2018-12-27 18:28:48 +01:00
if addLdapSSHPublicKeys ( usr , s , su . SSHPublicKey ) {
2018-05-24 06:59:02 +02:00
sshKeysNeedUpdate = true
}
2017-05-10 15:10:18 +02:00
}
} else if updateExisting {
existingUsers = append ( existingUsers , usr . ID )
2018-05-24 06:59:02 +02:00
// Synchronize SSH Public Key if that attribute is set
2018-12-27 18:28:48 +01:00
if isAttributeSSHPublicKeySet && synchronizeLdapSSHPublicKeys ( usr , s , su . SSHPublicKey ) {
2018-05-24 06:59:02 +02:00
sshKeysNeedUpdate = true
}
2017-05-10 15:10:18 +02:00
// Check if user data has changed
if ( len ( s . LDAP ( ) . AdminFilter ) > 0 && usr . IsAdmin != su . IsAdmin ) ||
2020-03-05 07:30:33 +01:00
( len ( s . LDAP ( ) . RestrictedFilter ) > 0 && usr . IsRestricted != su . IsRestricted ) ||
2019-06-12 21:41:28 +02:00
! strings . EqualFold ( usr . Email , su . Mail ) ||
2017-05-10 15:10:18 +02:00
usr . FullName != fullName ||
! usr . IsActive {
log . Trace ( "SyncExternalUsers[%s]: Updating user %s" , s . Name , usr . Name )
usr . FullName = fullName
usr . Email = su . Mail
// Change existing admin flag only if AdminFilter option is set
if len ( s . LDAP ( ) . AdminFilter ) > 0 {
usr . IsAdmin = su . IsAdmin
}
2020-03-05 07:30:33 +01:00
// Change existing restricted flag only if RestrictedFilter option is set
if ! usr . IsAdmin && len ( s . LDAP ( ) . RestrictedFilter ) > 0 {
usr . IsRestricted = su . IsRestricted
}
2017-05-10 15:10:18 +02:00
usr . IsActive = true
2020-03-05 07:30:33 +01:00
err = UpdateUserCols ( usr , "full_name" , "email" , "is_admin" , "is_restricted" , "is_active" )
2017-05-10 15:10:18 +02:00
if err != nil {
2019-04-02 09:48:31 +02:00
log . Error ( "SyncExternalUsers[%s]: Error updating user %s: %v" , s . Name , usr . Name , err )
2017-05-10 15:10:18 +02:00
}
}
}
}
2018-05-24 06:59:02 +02:00
// Rewrite authorized_keys file if LDAP Public SSH Key attribute is set and any key was added or removed
if sshKeysNeedUpdate {
2019-06-12 21:41:28 +02:00
err = RewriteAllPublicKeys ( )
if err != nil {
log . Error ( "RewriteAllPublicKeys: %v" , err )
}
2018-05-24 06:59:02 +02:00
}
2019-12-15 10:51:28 +01:00
select {
case <- ctx . Done ( ) :
2020-05-17 01:31:38 +02:00
log . Warn ( "SyncExternalUsers: Cancelled during update of %s before delete users" , s . Name )
return ErrCancelledf ( "During update of %s before delete users" , s . Name )
2019-12-15 10:51:28 +01:00
default :
}
2017-05-10 15:10:18 +02:00
// Deactivate users not present in LDAP
if updateExisting {
for _ , usr := range users {
found := false
for _ , uid := range existingUsers {
if usr . ID == uid {
found = true
break
}
}
if ! found {
log . Trace ( "SyncExternalUsers[%s]: Deactivating user %s" , s . Name , usr . Name )
usr . IsActive = false
2019-05-28 17:45:54 +02:00
err = UpdateUserCols ( usr , "is_active" )
2017-05-10 15:10:18 +02:00
if err != nil {
2019-04-02 09:48:31 +02:00
log . Error ( "SyncExternalUsers[%s]: Error deactivating user %s: %v" , s . Name , usr . Name , err )
2017-05-10 15:10:18 +02:00
}
}
}
}
}
}
2020-05-17 01:31:38 +02:00
return nil
2017-05-10 15:10:18 +02:00
}
2020-10-14 15:07:51 +02:00
// IterateUser iterate users
func IterateUser ( f func ( user * User ) error ) error {
var start int
var batchSize = setting . Database . IterateBufferSize
for {
var users = make ( [ ] * User , 0 , batchSize )
if err := x . Limit ( batchSize , start ) . Find ( & users ) ; err != nil {
return err
}
if len ( users ) == 0 {
return nil
}
start += len ( users )
for _ , user := range users {
if err := f ( user ) ; err != nil {
return err
}
}
}
}