gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  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/optional"
  11. "code.gitea.io/gitea/modules/structs"
  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 optional.Option[bool]
  29. IsAdmin optional.Option[bool]
  30. IsRestricted optional.Option[bool]
  31. IsTwoFactorEnabled optional.Option[bool]
  32. IsProhibitLogin optional.Option[bool]
  33. IncludeReserved bool
  34. }
  35. func (opts *SearchUserOptions) toSearchQueryBase(ctx context.Context) *xorm.Session {
  36. var cond builder.Cond
  37. cond = builder.Eq{"type": opts.Type}
  38. if opts.IncludeReserved {
  39. switch opts.Type {
  40. case UserTypeIndividual:
  41. cond = cond.Or(builder.Eq{"type": UserTypeUserReserved}).Or(
  42. builder.Eq{"type": UserTypeBot},
  43. ).Or(
  44. builder.Eq{"type": UserTypeRemoteUser},
  45. )
  46. case 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. var emailCond builder.Cond
  58. emailCond = builder.Like{"LOWER(email)", lowerKeyword}
  59. if opts.Actor == nil {
  60. emailCond = emailCond.And(builder.Eq{"keep_email_private": false})
  61. } else if !opts.Actor.IsAdmin {
  62. emailCond = emailCond.And(
  63. builder.Or(
  64. builder.Eq{"keep_email_private": false},
  65. builder.Eq{"id": opts.Actor.ID},
  66. ),
  67. )
  68. }
  69. keywordCond = keywordCond.Or(emailCond)
  70. }
  71. cond = cond.And(keywordCond)
  72. }
  73. // If visibility filtered
  74. if len(opts.Visible) > 0 {
  75. cond = cond.And(builder.In("visibility", opts.Visible))
  76. }
  77. cond = cond.And(BuildCanSeeUserCondition(opts.Actor))
  78. if opts.UID > 0 {
  79. cond = cond.And(builder.Eq{"id": opts.UID})
  80. }
  81. if opts.SourceID > 0 {
  82. cond = cond.And(builder.Eq{"login_source": opts.SourceID})
  83. }
  84. if opts.LoginName != "" {
  85. cond = cond.And(builder.Eq{"login_name": opts.LoginName})
  86. }
  87. if opts.IsActive.Has() {
  88. cond = cond.And(builder.Eq{"is_active": opts.IsActive.Value()})
  89. }
  90. if opts.IsAdmin.Has() {
  91. cond = cond.And(builder.Eq{"is_admin": opts.IsAdmin.Value()})
  92. }
  93. if opts.IsRestricted.Has() {
  94. cond = cond.And(builder.Eq{"is_restricted": opts.IsRestricted.Value()})
  95. }
  96. if opts.IsProhibitLogin.Has() {
  97. cond = cond.And(builder.Eq{"prohibit_login": opts.IsProhibitLogin.Value()})
  98. }
  99. e := db.GetEngine(ctx)
  100. if !opts.IsTwoFactorEnabled.Has() {
  101. return e.Where(cond)
  102. }
  103. // 2fa filter uses LEFT JOIN to check whether a user has a 2fa record
  104. // While using LEFT JOIN, sometimes the performance might not be good, but it won't be a problem now, such SQL is seldom executed.
  105. // There are some possible methods to refactor this SQL in future when we really need to optimize the performance (but not now):
  106. // (1) add a column in user table (2) add a setting value in user_setting table (3) use search engines (bleve/elasticsearch)
  107. if opts.IsTwoFactorEnabled.Value() {
  108. cond = cond.And(builder.Expr("two_factor.uid IS NOT NULL"))
  109. } else {
  110. cond = cond.And(builder.Expr("two_factor.uid IS NULL"))
  111. }
  112. return e.Join("LEFT OUTER", "two_factor", "two_factor.uid = `user`.id").
  113. Where(cond)
  114. }
  115. // SearchUsers takes options i.e. keyword and part of user name to search,
  116. // it returns results in given range and number of total results.
  117. func SearchUsers(ctx context.Context, opts SearchUserOptions) (users []*User, _ int64, _ error) {
  118. sessCount := opts.toSearchQueryBase(ctx)
  119. defer sessCount.Close()
  120. count, err := sessCount.Count(new(User))
  121. if err != nil {
  122. return nil, 0, fmt.Errorf("count: %w", err)
  123. }
  124. if len(opts.OrderBy) == 0 {
  125. opts.OrderBy = db.SearchOrderByAlphabetically
  126. }
  127. sessQuery := opts.toSearchQueryBase(ctx).OrderBy(opts.OrderBy.String())
  128. defer sessQuery.Close()
  129. if opts.Page > 0 {
  130. sessQuery = db.SetSessionPagination(sessQuery, &opts)
  131. }
  132. // the sql may contain JOIN, so we must only select User related columns
  133. sessQuery = sessQuery.Select("`user`.*")
  134. users = make([]*User, 0, opts.PageSize)
  135. return users, count, sessQuery.Find(&users)
  136. }
  137. // BuildCanSeeUserCondition creates a condition which can be used to restrict results to users/orgs the actor can see
  138. func BuildCanSeeUserCondition(actor *User) builder.Cond {
  139. if actor != nil {
  140. // If Admin - they see all users!
  141. if !actor.IsAdmin {
  142. // Users can see an organization they are a member of
  143. cond := builder.In("`user`.id", builder.Select("org_id").From("org_user").Where(builder.Eq{"uid": actor.ID}))
  144. if !actor.IsRestricted {
  145. // Not-Restricted users can see public and limited users/organizations
  146. cond = cond.Or(builder.In("`user`.visibility", structs.VisibleTypePublic, structs.VisibleTypeLimited))
  147. }
  148. // Don't forget about self
  149. return cond.Or(builder.Eq{"`user`.id": actor.ID})
  150. }
  151. return nil
  152. }
  153. // Force visibility for privacy
  154. // Not logged in - only public users
  155. return builder.In("`user`.visibility", structs.VisibleTypePublic)
  156. }