You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

search.go 5.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. // Copyright 2021 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package user
  4. import (
  5. "context"
  6. "fmt"
  7. "strings"
  8. "code.gitea.io/gitea/models/db"
  9. "code.gitea.io/gitea/modules/structs"
  10. "code.gitea.io/gitea/modules/util"
  11. "xorm.io/builder"
  12. "xorm.io/xorm"
  13. )
  14. // SearchUserOptions contains the options for searching
  15. type SearchUserOptions struct {
  16. db.ListOptions
  17. Keyword string
  18. Type UserType
  19. UID int64
  20. LoginName string // this option should be used only for admin user
  21. SourceID int64 // this option should be used only for admin user
  22. OrderBy db.SearchOrderBy
  23. Visible []structs.VisibleType
  24. Actor *User // The user doing the search
  25. SearchByEmail bool // Search by email as well as username/full name
  26. IsActive util.OptionalBool
  27. IsAdmin util.OptionalBool
  28. IsRestricted util.OptionalBool
  29. IsTwoFactorEnabled util.OptionalBool
  30. IsProhibitLogin util.OptionalBool
  31. IncludeReserved bool
  32. ExtraParamStrings map[string]string
  33. }
  34. func (opts *SearchUserOptions) toSearchQueryBase(ctx context.Context) *xorm.Session {
  35. var cond builder.Cond
  36. cond = builder.Eq{"type": opts.Type}
  37. if opts.IncludeReserved {
  38. if opts.Type == UserTypeIndividual {
  39. cond = cond.Or(builder.Eq{"type": UserTypeUserReserved}).Or(
  40. builder.Eq{"type": UserTypeBot},
  41. ).Or(
  42. builder.Eq{"type": UserTypeRemoteUser},
  43. )
  44. } else if opts.Type == UserTypeOrganization {
  45. cond = cond.Or(builder.Eq{"type": UserTypeOrganizationReserved})
  46. }
  47. }
  48. if len(opts.Keyword) > 0 {
  49. lowerKeyword := strings.ToLower(opts.Keyword)
  50. keywordCond := builder.Or(
  51. builder.Like{"lower_name", lowerKeyword},
  52. builder.Like{"LOWER(full_name)", lowerKeyword},
  53. )
  54. if opts.SearchByEmail {
  55. keywordCond = keywordCond.Or(builder.Like{"LOWER(email)", lowerKeyword})
  56. }
  57. cond = cond.And(keywordCond)
  58. }
  59. // If visibility filtered
  60. if len(opts.Visible) > 0 {
  61. cond = cond.And(builder.In("visibility", opts.Visible))
  62. }
  63. cond = cond.And(BuildCanSeeUserCondition(opts.Actor))
  64. if opts.UID > 0 {
  65. cond = cond.And(builder.Eq{"id": opts.UID})
  66. }
  67. if opts.SourceID > 0 {
  68. cond = cond.And(builder.Eq{"login_source": opts.SourceID})
  69. }
  70. if opts.LoginName != "" {
  71. cond = cond.And(builder.Eq{"login_name": opts.LoginName})
  72. }
  73. if !opts.IsActive.IsNone() {
  74. cond = cond.And(builder.Eq{"is_active": opts.IsActive.IsTrue()})
  75. }
  76. if !opts.IsAdmin.IsNone() {
  77. cond = cond.And(builder.Eq{"is_admin": opts.IsAdmin.IsTrue()})
  78. }
  79. if !opts.IsRestricted.IsNone() {
  80. cond = cond.And(builder.Eq{"is_restricted": opts.IsRestricted.IsTrue()})
  81. }
  82. if !opts.IsProhibitLogin.IsNone() {
  83. cond = cond.And(builder.Eq{"prohibit_login": opts.IsProhibitLogin.IsTrue()})
  84. }
  85. e := db.GetEngine(ctx)
  86. if opts.IsTwoFactorEnabled.IsNone() {
  87. return e.Where(cond)
  88. }
  89. // 2fa filter uses LEFT JOIN to check whether a user has a 2fa record
  90. // While using LEFT JOIN, sometimes the performance might not be good, but it won't be a problem now, such SQL is seldom executed.
  91. // There are some possible methods to refactor this SQL in future when we really need to optimize the performance (but not now):
  92. // (1) add a column in user table (2) add a setting value in user_setting table (3) use search engines (bleve/elasticsearch)
  93. if opts.IsTwoFactorEnabled.IsTrue() {
  94. cond = cond.And(builder.Expr("two_factor.uid IS NOT NULL"))
  95. } else {
  96. cond = cond.And(builder.Expr("two_factor.uid IS NULL"))
  97. }
  98. return e.Join("LEFT OUTER", "two_factor", "two_factor.uid = `user`.id").
  99. Where(cond)
  100. }
  101. // SearchUsers takes options i.e. keyword and part of user name to search,
  102. // it returns results in given range and number of total results.
  103. func SearchUsers(ctx context.Context, opts *SearchUserOptions) (users []*User, _ int64, _ error) {
  104. sessCount := opts.toSearchQueryBase(ctx)
  105. defer sessCount.Close()
  106. count, err := sessCount.Count(new(User))
  107. if err != nil {
  108. return nil, 0, fmt.Errorf("Count: %w", err)
  109. }
  110. if len(opts.OrderBy) == 0 {
  111. opts.OrderBy = db.SearchOrderByAlphabetically
  112. }
  113. sessQuery := opts.toSearchQueryBase(ctx).OrderBy(opts.OrderBy.String())
  114. defer sessQuery.Close()
  115. if opts.Page != 0 {
  116. sessQuery = db.SetSessionPagination(sessQuery, opts)
  117. }
  118. // the sql may contain JOIN, so we must only select User related columns
  119. sessQuery = sessQuery.Select("`user`.*")
  120. users = make([]*User, 0, opts.PageSize)
  121. return users, count, sessQuery.Find(&users)
  122. }
  123. // BuildCanSeeUserCondition creates a condition which can be used to restrict results to users/orgs the actor can see
  124. func BuildCanSeeUserCondition(actor *User) builder.Cond {
  125. if actor != nil {
  126. // If Admin - they see all users!
  127. if !actor.IsAdmin {
  128. // Users can see an organization they are a member of
  129. cond := builder.In("`user`.id", builder.Select("org_id").From("org_user").Where(builder.Eq{"uid": actor.ID}))
  130. if !actor.IsRestricted {
  131. // Not-Restricted users can see public and limited users/organizations
  132. cond = cond.Or(builder.In("`user`.visibility", structs.VisibleTypePublic, structs.VisibleTypeLimited))
  133. }
  134. // Don't forget about self
  135. return cond.Or(builder.Eq{"`user`.id": actor.ID})
  136. }
  137. return nil
  138. }
  139. // Force visibility for privacy
  140. // Not logged in - only public users
  141. return builder.In("`user`.visibility", structs.VisibleTypePublic)
  142. }