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

user_mail.go 14 kB

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