2021-11-24 12:49:20 +03:00
// Copyright 2021 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2021-11-24 12:49:20 +03:00
package user
import (
2023-09-14 20:09:32 +03:00
"context"
2021-11-24 12:49:20 +03:00
"fmt"
"strings"
"code.gitea.io/gitea/models/db"
2024-02-27 12:10:51 +03:00
"code.gitea.io/gitea/modules/container"
2024-02-29 21:52:49 +03:00
"code.gitea.io/gitea/modules/optional"
2021-11-24 12:49:20 +03:00
"code.gitea.io/gitea/modules/structs"
"xorm.io/builder"
"xorm.io/xorm"
)
// SearchUserOptions contains the options for searching
type SearchUserOptions struct {
db . ListOptions
2022-03-02 18:30:14 +03:00
2021-11-24 12:49:20 +03:00
Keyword string
Type UserType
UID int64
2023-03-15 14:53:01 +03:00
LoginName string // this option should be used only for admin user
SourceID int64 // this option should be used only for admin user
2021-11-24 12:49:20 +03:00
OrderBy db . SearchOrderBy
Visible [ ] structs . VisibleType
Actor * User // The user doing the search
SearchByEmail bool // Search by email as well as username/full name
2024-02-27 12:10:51 +03:00
SupportedSortOrders container . Set [ string ] // if not nil, only allow to use the sort orders in this set
2024-02-29 21:52:49 +03:00
IsActive optional . Option [ bool ]
IsAdmin optional . Option [ bool ]
IsRestricted optional . Option [ bool ]
IsTwoFactorEnabled optional . Option [ bool ]
IsProhibitLogin optional . Option [ bool ]
2023-09-14 09:53:36 +03:00
IncludeReserved bool
2022-03-02 18:30:14 +03:00
ExtraParamStrings map [ string ] string
2021-11-24 12:49:20 +03:00
}
2023-09-14 20:09:32 +03:00
func ( opts * SearchUserOptions ) toSearchQueryBase ( ctx context . Context ) * xorm . Session {
2023-09-14 09:53:36 +03:00
var cond builder . Cond
cond = builder . Eq { "type" : opts . Type }
if opts . IncludeReserved {
if opts . Type == UserTypeIndividual {
cond = cond . Or ( builder . Eq { "type" : UserTypeUserReserved } ) . Or (
builder . Eq { "type" : UserTypeBot } ,
) . Or (
builder . Eq { "type" : UserTypeRemoteUser } ,
)
} else if opts . Type == UserTypeOrganization {
cond = cond . Or ( builder . Eq { "type" : UserTypeOrganizationReserved } )
}
}
2021-11-24 12:49:20 +03:00
if len ( opts . Keyword ) > 0 {
lowerKeyword := strings . ToLower ( opts . Keyword )
keywordCond := builder . Or (
builder . Like { "lower_name" , lowerKeyword } ,
builder . Like { "LOWER(full_name)" , lowerKeyword } ,
)
if opts . SearchByEmail {
keywordCond = keywordCond . Or ( builder . Like { "LOWER(email)" , lowerKeyword } )
}
cond = cond . And ( keywordCond )
}
// If visibility filtered
if len ( opts . Visible ) > 0 {
cond = cond . And ( builder . In ( "visibility" , opts . Visible ) )
}
2022-07-28 06:59:39 +03:00
cond = cond . And ( BuildCanSeeUserCondition ( opts . Actor ) )
2021-11-24 12:49:20 +03:00
if opts . UID > 0 {
cond = cond . And ( builder . Eq { "id" : opts . UID } )
}
2023-03-15 14:53:01 +03:00
if opts . SourceID > 0 {
cond = cond . And ( builder . Eq { "login_source" : opts . SourceID } )
}
if opts . LoginName != "" {
cond = cond . And ( builder . Eq { "login_name" : opts . LoginName } )
}
2024-02-29 21:52:49 +03:00
if opts . IsActive . Has ( ) {
cond = cond . And ( builder . Eq { "is_active" : opts . IsActive . Value ( ) } )
2021-11-24 12:49:20 +03:00
}
2024-02-29 21:52:49 +03:00
if opts . IsAdmin . Has ( ) {
cond = cond . And ( builder . Eq { "is_admin" : opts . IsAdmin . Value ( ) } )
2021-11-24 12:49:20 +03:00
}
2024-02-29 21:52:49 +03:00
if opts . IsRestricted . Has ( ) {
cond = cond . And ( builder . Eq { "is_restricted" : opts . IsRestricted . Value ( ) } )
2021-11-24 12:49:20 +03:00
}
2024-02-29 21:52:49 +03:00
if opts . IsProhibitLogin . Has ( ) {
cond = cond . And ( builder . Eq { "prohibit_login" : opts . IsProhibitLogin . Value ( ) } )
2021-11-24 12:49:20 +03:00
}
2023-09-14 20:09:32 +03:00
e := db . GetEngine ( ctx )
2024-02-29 21:52:49 +03:00
if ! opts . IsTwoFactorEnabled . Has ( ) {
2021-11-24 12:49:20 +03:00
return e . Where ( cond )
}
// 2fa filter uses LEFT JOIN to check whether a user has a 2fa record
2022-01-13 23:32:28 +03:00
// While using LEFT JOIN, sometimes the performance might not be good, but it won't be a problem now, such SQL is seldom executed.
// There are some possible methods to refactor this SQL in future when we really need to optimize the performance (but not now):
// (1) add a column in user table (2) add a setting value in user_setting table (3) use search engines (bleve/elasticsearch)
2024-02-29 21:52:49 +03:00
if opts . IsTwoFactorEnabled . Value ( ) {
2021-11-24 12:49:20 +03:00
cond = cond . And ( builder . Expr ( "two_factor.uid IS NOT NULL" ) )
} else {
cond = cond . And ( builder . Expr ( "two_factor.uid IS NULL" ) )
}
return e . Join ( "LEFT OUTER" , "two_factor" , "two_factor.uid = `user`.id" ) .
Where ( cond )
}
// SearchUsers takes options i.e. keyword and part of user name to search,
// it returns results in given range and number of total results.
2023-09-14 20:09:32 +03:00
func SearchUsers ( ctx context . Context , opts * SearchUserOptions ) ( users [ ] * User , _ int64 , _ error ) {
sessCount := opts . toSearchQueryBase ( ctx )
2021-11-24 12:49:20 +03:00
defer sessCount . Close ( )
count , err := sessCount . Count ( new ( User ) )
if err != nil {
2024-02-29 21:52:49 +03:00
return nil , 0 , fmt . Errorf ( "count: %w" , err )
2021-11-24 12:49:20 +03:00
}
if len ( opts . OrderBy ) == 0 {
opts . OrderBy = db . SearchOrderByAlphabetically
}
2023-09-14 20:09:32 +03:00
sessQuery := opts . toSearchQueryBase ( ctx ) . OrderBy ( opts . OrderBy . String ( ) )
2021-11-24 12:49:20 +03:00
defer sessQuery . Close ( )
if opts . Page != 0 {
sessQuery = db . SetSessionPagination ( sessQuery , opts )
}
// the sql may contain JOIN, so we must only select User related columns
sessQuery = sessQuery . Select ( "`user`.*" )
users = make ( [ ] * User , 0 , opts . PageSize )
return users , count , sessQuery . Find ( & users )
}
2022-07-28 06:59:39 +03:00
// BuildCanSeeUserCondition creates a condition which can be used to restrict results to users/orgs the actor can see
func BuildCanSeeUserCondition ( actor * User ) builder . Cond {
if actor != nil {
// If Admin - they see all users!
if ! actor . IsAdmin {
// Users can see an organization they are a member of
cond := builder . In ( "`user`.id" , builder . Select ( "org_id" ) . From ( "org_user" ) . Where ( builder . Eq { "uid" : actor . ID } ) )
if ! actor . IsRestricted {
// Not-Restricted users can see public and limited users/organizations
cond = cond . Or ( builder . In ( "`user`.visibility" , structs . VisibleTypePublic , structs . VisibleTypeLimited ) )
}
// Don't forget about self
return cond . Or ( builder . Eq { "`user`.id" : actor . ID } )
}
return nil
}
// Force visibility for privacy
// Not logged in - only public users
return builder . In ( "`user`.visibility" , structs . VisibleTypePublic )
}