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.

479 lines
12 KiB

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