Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

search.go 5.3KB

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