A fork of Gitea (see branch `mj`) adding Majority Judgment Polls 𐄷 over Issues and Merge Requests. https://git.mieuxvoter.fr
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.

500 lines
13 KiB

6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
  1. // Copyright 2016 The Gogs Authors. All rights reserved.
  2. // Copyright 2020 The Gitea Authors. All rights reserved.
  3. // Use of this source code is governed by a MIT-style
  4. // license that can be found in the LICENSE file.
  5. package models
  6. import (
  7. "errors"
  8. "fmt"
  9. "net/mail"
  10. "strings"
  11. "code.gitea.io/gitea/modules/log"
  12. "code.gitea.io/gitea/modules/setting"
  13. "code.gitea.io/gitea/modules/util"
  14. "xorm.io/builder"
  15. )
  16. // ErrEmailAddressNotExist email address not exist
  17. var ErrEmailAddressNotExist = errors.New("Email address does not exist")
  18. // EmailAddress is the list of all email addresses of a user. Can contain the
  19. // primary email address, but is not obligatory.
  20. type EmailAddress struct {
  21. ID int64 `xorm:"pk autoincr"`
  22. UID int64 `xorm:"INDEX NOT NULL"`
  23. Email string `xorm:"UNIQUE NOT NULL"`
  24. IsActivated bool
  25. IsPrimary bool `xorm:"-"`
  26. }
  27. // ValidateEmail check if email is a allowed address
  28. func ValidateEmail(email string) error {
  29. if len(email) == 0 {
  30. return nil
  31. }
  32. if _, err := mail.ParseAddress(email); err != nil {
  33. return ErrEmailInvalid{email}
  34. }
  35. // TODO: add an email allow/block list
  36. return nil
  37. }
  38. // GetEmailAddresses returns all email addresses belongs to given user.
  39. func GetEmailAddresses(uid int64) ([]*EmailAddress, error) {
  40. emails := make([]*EmailAddress, 0, 5)
  41. if err := x.
  42. Where("uid=?", uid).
  43. Find(&emails); err != nil {
  44. return nil, err
  45. }
  46. u, err := GetUserByID(uid)
  47. if err != nil {
  48. return nil, err
  49. }
  50. isPrimaryFound := false
  51. for _, email := range emails {
  52. if email.Email == u.Email {
  53. isPrimaryFound = true
  54. email.IsPrimary = true
  55. } else {
  56. email.IsPrimary = false
  57. }
  58. }
  59. // We always want the primary email address displayed, even if it's not in
  60. // the email address table (yet).
  61. if !isPrimaryFound {
  62. emails = append(emails, &EmailAddress{
  63. Email: u.Email,
  64. IsActivated: u.IsActive,
  65. IsPrimary: true,
  66. })
  67. }
  68. return emails, nil
  69. }
  70. // GetEmailAddressByID gets a user's email address by ID
  71. func GetEmailAddressByID(uid, id int64) (*EmailAddress, error) {
  72. // User ID is required for security reasons
  73. email := &EmailAddress{UID: uid}
  74. if has, err := x.ID(id).Get(email); err != nil {
  75. return nil, err
  76. } else if !has {
  77. return nil, nil
  78. }
  79. return email, nil
  80. }
  81. func isEmailActive(e Engine, email string, userID, emailID int64) (bool, error) {
  82. if len(email) == 0 {
  83. return true, nil
  84. }
  85. // Can't filter by boolean field unless it's explicit
  86. cond := builder.NewCond()
  87. cond = cond.And(builder.Eq{"email": email}, builder.Neq{"id": emailID})
  88. if setting.Service.RegisterEmailConfirm {
  89. // Inactive (unvalidated) addresses don't count as active if email validation is required
  90. cond = cond.And(builder.Eq{"is_activated": true})
  91. }
  92. em := EmailAddress{}
  93. if has, err := e.Where(cond).Get(&em); has || err != nil {
  94. if has {
  95. log.Info("isEmailActive('%s',%d,%d) found duplicate in email ID %d", email, userID, emailID, em.ID)
  96. }
  97. return has, err
  98. }
  99. // Can't filter by boolean field unless it's explicit
  100. cond = builder.NewCond()
  101. cond = cond.And(builder.Eq{"email": email}, builder.Neq{"id": userID})
  102. if setting.Service.RegisterEmailConfirm {
  103. cond = cond.And(builder.Eq{"is_active": true})
  104. }
  105. us := User{}
  106. if has, err := e.Where(cond).Get(&us); has || err != nil {
  107. if has {
  108. log.Info("isEmailActive('%s',%d,%d) found duplicate in user ID %d", email, userID, emailID, us.ID)
  109. }
  110. return has, err
  111. }
  112. return false, nil
  113. }
  114. func isEmailUsed(e Engine, email string) (bool, error) {
  115. if len(email) == 0 {
  116. return true, nil
  117. }
  118. return e.Where("email=?", email).Get(&EmailAddress{})
  119. }
  120. // IsEmailUsed returns true if the email has been used.
  121. func IsEmailUsed(email string) (bool, error) {
  122. return isEmailUsed(x, email)
  123. }
  124. func addEmailAddress(e Engine, email *EmailAddress) error {
  125. email.Email = strings.ToLower(strings.TrimSpace(email.Email))
  126. used, err := isEmailUsed(e, email.Email)
  127. if err != nil {
  128. return err
  129. } else if used {
  130. return ErrEmailAlreadyUsed{email.Email}
  131. }
  132. if err = ValidateEmail(email.Email); err != nil {
  133. return err
  134. }
  135. _, err = e.Insert(email)
  136. return err
  137. }
  138. // AddEmailAddress adds an email address to given user.
  139. func AddEmailAddress(email *EmailAddress) error {
  140. return addEmailAddress(x, email)
  141. }
  142. // AddEmailAddresses adds an email address to given user.
  143. func AddEmailAddresses(emails []*EmailAddress) error {
  144. if len(emails) == 0 {
  145. return nil
  146. }
  147. // Check if any of them has been used
  148. for i := range emails {
  149. emails[i].Email = strings.ToLower(strings.TrimSpace(emails[i].Email))
  150. used, err := IsEmailUsed(emails[i].Email)
  151. if err != nil {
  152. return err
  153. } else if used {
  154. return ErrEmailAlreadyUsed{emails[i].Email}
  155. }
  156. if err = ValidateEmail(emails[i].Email); err != nil {
  157. return err
  158. }
  159. }
  160. if _, err := x.Insert(emails); err != nil {
  161. return fmt.Errorf("Insert: %v", err)
  162. }
  163. return nil
  164. }
  165. // Activate activates the email address to given user.
  166. func (email *EmailAddress) Activate() error {
  167. sess := x.NewSession()
  168. defer sess.Close()
  169. if err := sess.Begin(); err != nil {
  170. return err
  171. }
  172. if err := email.updateActivation(sess, true); err != nil {
  173. return err
  174. }
  175. return sess.Commit()
  176. }
  177. func (email *EmailAddress) updateActivation(e Engine, activate bool) error {
  178. user, err := getUserByID(e, email.UID)
  179. if err != nil {
  180. return err
  181. }
  182. if user.Rands, err = GetUserSalt(); err != nil {
  183. return err
  184. }
  185. email.IsActivated = activate
  186. if _, err := e.ID(email.ID).Cols("is_activated").Update(email); err != nil {
  187. return err
  188. }
  189. return updateUserCols(e, user, "rands")
  190. }
  191. // DeleteEmailAddress deletes an email address of given user.
  192. func DeleteEmailAddress(email *EmailAddress) (err error) {
  193. var deleted int64
  194. // ask to check UID
  195. address := EmailAddress{
  196. UID: email.UID,
  197. }
  198. if email.ID > 0 {
  199. deleted, err = x.ID(email.ID).Delete(&address)
  200. } else {
  201. deleted, err = x.
  202. Where("email=?", email.Email).
  203. Delete(&address)
  204. }
  205. if err != nil {
  206. return err
  207. } else if deleted != 1 {
  208. return ErrEmailAddressNotExist
  209. }
  210. return nil
  211. }
  212. // DeleteEmailAddresses deletes multiple email addresses
  213. func DeleteEmailAddresses(emails []*EmailAddress) (err error) {
  214. for i := range emails {
  215. if err = DeleteEmailAddress(emails[i]); err != nil {
  216. return err
  217. }
  218. }
  219. return nil
  220. }
  221. // MakeEmailPrimary sets primary email address of given user.
  222. func MakeEmailPrimary(email *EmailAddress) error {
  223. has, err := x.Get(email)
  224. if err != nil {
  225. return err
  226. } else if !has {
  227. return ErrEmailNotExist
  228. }
  229. if !email.IsActivated {
  230. return ErrEmailNotActivated
  231. }
  232. user := &User{}
  233. has, err = x.ID(email.UID).Get(user)
  234. if err != nil {
  235. return err
  236. } else if !has {
  237. return ErrUserNotExist{email.UID, "", 0}
  238. }
  239. // Make sure the former primary email doesn't disappear.
  240. formerPrimaryEmail := &EmailAddress{UID: user.ID, Email: user.Email}
  241. has, err = x.Get(formerPrimaryEmail)
  242. if err != nil {
  243. return err
  244. }
  245. sess := x.NewSession()
  246. defer sess.Close()
  247. if err = sess.Begin(); err != nil {
  248. return err
  249. }
  250. if !has {
  251. formerPrimaryEmail.UID = user.ID
  252. formerPrimaryEmail.IsActivated = user.IsActive
  253. if _, err = sess.Insert(formerPrimaryEmail); err != nil {
  254. return err
  255. }
  256. }
  257. user.Email = email.Email
  258. if _, err = sess.ID(user.ID).Cols("email").Update(user); err != nil {
  259. return err
  260. }
  261. return sess.Commit()
  262. }
  263. // SearchEmailOrderBy is used to sort the results from SearchEmails()
  264. type SearchEmailOrderBy string
  265. func (s SearchEmailOrderBy) String() string {
  266. return string(s)
  267. }
  268. // Strings for sorting result
  269. const (
  270. SearchEmailOrderByEmail SearchEmailOrderBy = "emails.email ASC, is_primary DESC, sortid ASC"
  271. SearchEmailOrderByEmailReverse SearchEmailOrderBy = "emails.email DESC, is_primary ASC, sortid DESC"
  272. SearchEmailOrderByName SearchEmailOrderBy = "`user`.lower_name ASC, is_primary DESC, sortid ASC"
  273. SearchEmailOrderByNameReverse SearchEmailOrderBy = "`user`.lower_name DESC, is_primary ASC, sortid DESC"
  274. )
  275. // SearchEmailOptions are options to search e-mail addresses for the admin panel
  276. type SearchEmailOptions struct {
  277. ListOptions
  278. Keyword string
  279. SortType SearchEmailOrderBy
  280. IsPrimary util.OptionalBool
  281. IsActivated util.OptionalBool
  282. }
  283. // SearchEmailResult is an e-mail address found in the user or email_address table
  284. type SearchEmailResult struct {
  285. UID int64
  286. Email string
  287. IsActivated bool
  288. IsPrimary bool
  289. // From User
  290. Name string
  291. FullName string
  292. }
  293. // SearchEmails takes options i.e. keyword and part of email name to search,
  294. // it returns results in given range and number of total results.
  295. func SearchEmails(opts *SearchEmailOptions) ([]*SearchEmailResult, int64, error) {
  296. // Unfortunately, UNION support for SQLite in xorm is currently broken, so we must
  297. // build the SQL ourselves.
  298. where := make([]string, 0, 5)
  299. args := make([]interface{}, 0, 5)
  300. emailsSQL := "(SELECT id as sortid, uid, email, is_activated, 0 as is_primary " +
  301. "FROM email_address " +
  302. "UNION ALL " +
  303. "SELECT id as sortid, id AS uid, email, is_active AS is_activated, 1 as is_primary " +
  304. "FROM `user` " +
  305. "WHERE type = ?) AS emails"
  306. args = append(args, UserTypeIndividual)
  307. if len(opts.Keyword) > 0 {
  308. // Note: % can be injected in the Keyword parameter, but it won't do any harm.
  309. where = append(where, "(lower(`user`.full_name) LIKE ? OR `user`.lower_name LIKE ? OR emails.email LIKE ?)")
  310. likeStr := "%" + strings.ToLower(opts.Keyword) + "%"
  311. args = append(args, likeStr)
  312. args = append(args, likeStr)
  313. args = append(args, likeStr)
  314. }
  315. switch {
  316. case opts.IsPrimary.IsTrue():
  317. where = append(where, "emails.is_primary = ?")
  318. args = append(args, true)
  319. case opts.IsPrimary.IsFalse():
  320. where = append(where, "emails.is_primary = ?")
  321. args = append(args, false)
  322. }
  323. switch {
  324. case opts.IsActivated.IsTrue():
  325. where = append(where, "emails.is_activated = ?")
  326. args = append(args, true)
  327. case opts.IsActivated.IsFalse():
  328. where = append(where, "emails.is_activated = ?")
  329. args = append(args, false)
  330. }
  331. var whereStr string
  332. if len(where) > 0 {
  333. whereStr = "WHERE " + strings.Join(where, " AND ")
  334. }
  335. joinSQL := "FROM " + emailsSQL + " INNER JOIN `user` ON `user`.id = emails.uid " + whereStr
  336. count, err := x.SQL("SELECT count(*) "+joinSQL, args...).Count()
  337. if err != nil {
  338. return nil, 0, fmt.Errorf("Count: %v", err)
  339. }
  340. orderby := opts.SortType.String()
  341. if orderby == "" {
  342. orderby = SearchEmailOrderByEmail.String()
  343. }
  344. querySQL := "SELECT emails.uid, emails.email, emails.is_activated, emails.is_primary, " +
  345. "`user`.name, `user`.full_name " + joinSQL + " ORDER BY " + orderby
  346. opts.setDefaultValues()
  347. rows, err := x.SQL(querySQL, args...).Rows(new(SearchEmailResult))
  348. if err != nil {
  349. return nil, 0, fmt.Errorf("Emails: %v", err)
  350. }
  351. // Page manually because xorm can't handle Limit() with raw SQL
  352. defer rows.Close()
  353. emails := make([]*SearchEmailResult, 0, opts.PageSize)
  354. skip := (opts.Page - 1) * opts.PageSize
  355. for rows.Next() {
  356. var email SearchEmailResult
  357. if err := rows.Scan(&email); err != nil {
  358. return nil, 0, err
  359. }
  360. if skip > 0 {
  361. skip--
  362. continue
  363. }
  364. emails = append(emails, &email)
  365. if len(emails) == opts.PageSize {
  366. break
  367. }
  368. }
  369. return emails, count, err
  370. }
  371. // ActivateUserEmail will change the activated state of an email address,
  372. // either primary (in the user table) or secondary (in the email_address table)
  373. func ActivateUserEmail(userID int64, email string, primary, activate bool) (err error) {
  374. sess := x.NewSession()
  375. defer sess.Close()
  376. if err = sess.Begin(); err != nil {
  377. return err
  378. }
  379. if primary {
  380. // Activate/deactivate a user's primary email address
  381. user := User{ID: userID, Email: email}
  382. if has, err := sess.Get(&user); err != nil {
  383. return err
  384. } else if !has {
  385. return fmt.Errorf("no such user: %d (%s)", userID, email)
  386. }
  387. if user.IsActive == activate {
  388. // Already in the desired state; no action
  389. return nil
  390. }
  391. if activate {
  392. if used, err := isEmailActive(sess, email, userID, 0); err != nil {
  393. return fmt.Errorf("isEmailActive(): %v", err)
  394. } else if used {
  395. return ErrEmailAlreadyUsed{Email: email}
  396. }
  397. }
  398. user.IsActive = activate
  399. if user.Rands, err = GetUserSalt(); err != nil {
  400. return fmt.Errorf("generate salt: %v", err)
  401. }
  402. if err = updateUserCols(sess, &user, "is_active", "rands"); err != nil {
  403. return fmt.Errorf("updateUserCols(): %v", err)
  404. }
  405. } else {
  406. // Activate/deactivate a user's secondary email address
  407. // First check if there's another user active with the same address
  408. addr := EmailAddress{UID: userID, Email: email}
  409. if has, err := sess.Get(&addr); err != nil {
  410. return err
  411. } else if !has {
  412. return fmt.Errorf("no such email: %d (%s)", userID, email)
  413. }
  414. if addr.IsActivated == activate {
  415. // Already in the desired state; no action
  416. return nil
  417. }
  418. if activate {
  419. if used, err := isEmailActive(sess, email, 0, addr.ID); err != nil {
  420. return fmt.Errorf("isEmailActive(): %v", err)
  421. } else if used {
  422. return ErrEmailAlreadyUsed{Email: email}
  423. }
  424. }
  425. if err = addr.updateActivation(sess, activate); err != nil {
  426. return fmt.Errorf("updateActivation(): %v", err)
  427. }
  428. }
  429. return sess.Commit()
  430. }