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.

user_mail.go 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  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. "fmt"
  8. "net/mail"
  9. "strings"
  10. "code.gitea.io/gitea/models/db"
  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. // EmailAddress is the list of all email addresses of a user. It also contains the
  17. // primary email address which is saved in user table.
  18. type EmailAddress struct {
  19. ID int64 `xorm:"pk autoincr"`
  20. UID int64 `xorm:"INDEX NOT NULL"`
  21. Email string `xorm:"UNIQUE NOT NULL"`
  22. LowerEmail string `xorm:"UNIQUE NOT NULL"`
  23. IsActivated bool
  24. IsPrimary bool `xorm:"DEFAULT(false) NOT NULL"`
  25. }
  26. func init() {
  27. db.RegisterModel(new(EmailAddress))
  28. }
  29. // BeforeInsert will be invoked by XORM before inserting a record
  30. func (email *EmailAddress) BeforeInsert() {
  31. if email.LowerEmail == "" {
  32. email.LowerEmail = strings.ToLower(email.Email)
  33. }
  34. }
  35. // ValidateEmail check if email is a allowed address
  36. func ValidateEmail(email string) error {
  37. if len(email) == 0 {
  38. return nil
  39. }
  40. if _, err := mail.ParseAddress(email); err != nil {
  41. return ErrEmailInvalid{email}
  42. }
  43. // TODO: add an email allow/block list
  44. return nil
  45. }
  46. // GetEmailAddresses returns all email addresses belongs to given user.
  47. func GetEmailAddresses(uid int64) ([]*EmailAddress, error) {
  48. emails := make([]*EmailAddress, 0, 5)
  49. if err := db.GetEngine(db.DefaultContext).
  50. Where("uid=?", uid).
  51. Asc("id").
  52. Find(&emails); err != nil {
  53. return nil, err
  54. }
  55. return emails, nil
  56. }
  57. // GetEmailAddressByID gets a user's email address by ID
  58. func GetEmailAddressByID(uid, id int64) (*EmailAddress, error) {
  59. // User ID is required for security reasons
  60. email := &EmailAddress{UID: uid}
  61. if has, err := db.GetEngine(db.DefaultContext).ID(id).Get(email); err != nil {
  62. return nil, err
  63. } else if !has {
  64. return nil, nil
  65. }
  66. return email, nil
  67. }
  68. // isEmailActive check if email is activated with a different emailID
  69. func isEmailActive(e db.Engine, email string, excludeEmailID int64) (bool, error) {
  70. if len(email) == 0 {
  71. return true, nil
  72. }
  73. // Can't filter by boolean field unless it's explicit
  74. cond := builder.NewCond()
  75. cond = cond.And(builder.Eq{"lower_email": strings.ToLower(email)}, builder.Neq{"id": excludeEmailID})
  76. if setting.Service.RegisterEmailConfirm {
  77. // Inactive (unvalidated) addresses don't count as active if email validation is required
  78. cond = cond.And(builder.Eq{"is_activated": true})
  79. }
  80. var em EmailAddress
  81. if has, err := e.Where(cond).Get(&em); has || err != nil {
  82. if has {
  83. log.Info("isEmailActive(%q, %d) found duplicate in email ID %d", email, excludeEmailID, em.ID)
  84. }
  85. return has, err
  86. }
  87. return false, nil
  88. }
  89. func isEmailUsed(e db.Engine, email string) (bool, error) {
  90. if len(email) == 0 {
  91. return true, nil
  92. }
  93. return e.Where("lower_email=?", strings.ToLower(email)).Get(&EmailAddress{})
  94. }
  95. // IsEmailUsed returns true if the email has been used.
  96. func IsEmailUsed(email string) (bool, error) {
  97. return isEmailUsed(db.GetEngine(db.DefaultContext), email)
  98. }
  99. func addEmailAddress(e db.Engine, email *EmailAddress) error {
  100. email.Email = strings.TrimSpace(email.Email)
  101. used, err := isEmailUsed(e, email.Email)
  102. if err != nil {
  103. return err
  104. } else if used {
  105. return ErrEmailAlreadyUsed{email.Email}
  106. }
  107. if err = ValidateEmail(email.Email); err != nil {
  108. return err
  109. }
  110. _, err = e.Insert(email)
  111. return err
  112. }
  113. // AddEmailAddress adds an email address to given user.
  114. func AddEmailAddress(email *EmailAddress) error {
  115. return addEmailAddress(db.GetEngine(db.DefaultContext), email)
  116. }
  117. // AddEmailAddresses adds an email address to given user.
  118. func AddEmailAddresses(emails []*EmailAddress) error {
  119. if len(emails) == 0 {
  120. return nil
  121. }
  122. // Check if any of them has been used
  123. for i := range emails {
  124. emails[i].Email = strings.TrimSpace(emails[i].Email)
  125. used, err := IsEmailUsed(emails[i].Email)
  126. if err != nil {
  127. return err
  128. } else if used {
  129. return ErrEmailAlreadyUsed{emails[i].Email}
  130. }
  131. if err = ValidateEmail(emails[i].Email); err != nil {
  132. return err
  133. }
  134. }
  135. if _, err := db.GetEngine(db.DefaultContext).Insert(emails); err != nil {
  136. return fmt.Errorf("Insert: %v", err)
  137. }
  138. return nil
  139. }
  140. // Activate activates the email address to given user.
  141. func (email *EmailAddress) Activate() error {
  142. sess := db.NewSession(db.DefaultContext)
  143. defer sess.Close()
  144. if err := sess.Begin(); err != nil {
  145. return err
  146. }
  147. if err := email.updateActivation(sess, true); err != nil {
  148. return err
  149. }
  150. return sess.Commit()
  151. }
  152. func (email *EmailAddress) updateActivation(e db.Engine, activate bool) error {
  153. user, err := getUserByID(e, email.UID)
  154. if err != nil {
  155. return err
  156. }
  157. if user.Rands, err = GetUserSalt(); err != nil {
  158. return err
  159. }
  160. email.IsActivated = activate
  161. if _, err := e.ID(email.ID).Cols("is_activated").Update(email); err != nil {
  162. return err
  163. }
  164. return updateUserCols(e, user, "rands")
  165. }
  166. // DeleteEmailAddress deletes an email address of given user.
  167. func DeleteEmailAddress(email *EmailAddress) (err error) {
  168. if email.IsPrimary {
  169. return ErrPrimaryEmailCannotDelete{Email: email.Email}
  170. }
  171. var deleted int64
  172. // ask to check UID
  173. address := EmailAddress{
  174. UID: email.UID,
  175. }
  176. if email.ID > 0 {
  177. deleted, err = db.GetEngine(db.DefaultContext).ID(email.ID).Delete(&address)
  178. } else {
  179. if email.Email != "" && email.LowerEmail == "" {
  180. email.LowerEmail = strings.ToLower(email.Email)
  181. }
  182. deleted, err = db.GetEngine(db.DefaultContext).
  183. Where("lower_email=?", email.LowerEmail).
  184. Delete(&address)
  185. }
  186. if err != nil {
  187. return err
  188. } else if deleted != 1 {
  189. return ErrEmailAddressNotExist{Email: email.Email}
  190. }
  191. return nil
  192. }
  193. // DeleteEmailAddresses deletes multiple email addresses
  194. func DeleteEmailAddresses(emails []*EmailAddress) (err error) {
  195. for i := range emails {
  196. if err = DeleteEmailAddress(emails[i]); err != nil {
  197. return err
  198. }
  199. }
  200. return nil
  201. }
  202. // MakeEmailPrimary sets primary email address of given user.
  203. func MakeEmailPrimary(email *EmailAddress) error {
  204. has, err := db.GetEngine(db.DefaultContext).Get(email)
  205. if err != nil {
  206. return err
  207. } else if !has {
  208. return ErrEmailAddressNotExist{Email: email.Email}
  209. }
  210. if !email.IsActivated {
  211. return ErrEmailNotActivated
  212. }
  213. user := &User{}
  214. has, err = db.GetEngine(db.DefaultContext).ID(email.UID).Get(user)
  215. if err != nil {
  216. return err
  217. } else if !has {
  218. return ErrUserNotExist{email.UID, "", 0}
  219. }
  220. sess := db.NewSession(db.DefaultContext)
  221. defer sess.Close()
  222. if err = sess.Begin(); err != nil {
  223. return err
  224. }
  225. // 1. Update user table
  226. user.Email = email.Email
  227. if _, err = sess.ID(user.ID).Cols("email").Update(user); err != nil {
  228. return err
  229. }
  230. // 2. Update old primary email
  231. if _, err = sess.Where("uid=? AND is_primary=?", email.UID, true).Cols("is_primary").Update(&EmailAddress{
  232. IsPrimary: false,
  233. }); err != nil {
  234. return err
  235. }
  236. // 3. update new primary email
  237. email.IsPrimary = true
  238. if _, err = sess.ID(email.ID).Cols("is_primary").Update(email); err != nil {
  239. return err
  240. }
  241. return sess.Commit()
  242. }
  243. // SearchEmailOrderBy is used to sort the results from SearchEmails()
  244. type SearchEmailOrderBy string
  245. func (s SearchEmailOrderBy) String() string {
  246. return string(s)
  247. }
  248. // Strings for sorting result
  249. const (
  250. SearchEmailOrderByEmail SearchEmailOrderBy = "email_address.lower_email ASC, email_address.is_primary DESC, email_address.id ASC"
  251. SearchEmailOrderByEmailReverse SearchEmailOrderBy = "email_address.lower_email DESC, email_address.is_primary ASC, email_address.id DESC"
  252. SearchEmailOrderByName SearchEmailOrderBy = "`user`.lower_name ASC, email_address.is_primary DESC, email_address.id ASC"
  253. SearchEmailOrderByNameReverse SearchEmailOrderBy = "`user`.lower_name DESC, email_address.is_primary ASC, email_address.id DESC"
  254. )
  255. // SearchEmailOptions are options to search e-mail addresses for the admin panel
  256. type SearchEmailOptions struct {
  257. db.ListOptions
  258. Keyword string
  259. SortType SearchEmailOrderBy
  260. IsPrimary util.OptionalBool
  261. IsActivated util.OptionalBool
  262. }
  263. // SearchEmailResult is an e-mail address found in the user or email_address table
  264. type SearchEmailResult struct {
  265. UID int64
  266. Email string
  267. IsActivated bool
  268. IsPrimary bool
  269. // From User
  270. Name string
  271. FullName string
  272. }
  273. // SearchEmails takes options i.e. keyword and part of email name to search,
  274. // it returns results in given range and number of total results.
  275. func SearchEmails(opts *SearchEmailOptions) ([]*SearchEmailResult, int64, error) {
  276. var cond builder.Cond = builder.Eq{"`user`.`type`": UserTypeIndividual}
  277. if len(opts.Keyword) > 0 {
  278. likeStr := "%" + strings.ToLower(opts.Keyword) + "%"
  279. cond = cond.And(builder.Or(
  280. builder.Like{"lower(`user`.full_name)", likeStr},
  281. builder.Like{"`user`.lower_name", likeStr},
  282. builder.Like{"email_address.lower_email", likeStr},
  283. ))
  284. }
  285. switch {
  286. case opts.IsPrimary.IsTrue():
  287. cond = cond.And(builder.Eq{"email_address.is_primary": true})
  288. case opts.IsPrimary.IsFalse():
  289. cond = cond.And(builder.Eq{"email_address.is_primary": false})
  290. }
  291. switch {
  292. case opts.IsActivated.IsTrue():
  293. cond = cond.And(builder.Eq{"email_address.is_activated": true})
  294. case opts.IsActivated.IsFalse():
  295. cond = cond.And(builder.Eq{"email_address.is_activated": false})
  296. }
  297. count, err := db.GetEngine(db.DefaultContext).Join("INNER", "`user`", "`user`.ID = email_address.uid").
  298. Where(cond).Count(new(EmailAddress))
  299. if err != nil {
  300. return nil, 0, fmt.Errorf("Count: %v", err)
  301. }
  302. orderby := opts.SortType.String()
  303. if orderby == "" {
  304. orderby = SearchEmailOrderByEmail.String()
  305. }
  306. opts.SetDefaultValues()
  307. emails := make([]*SearchEmailResult, 0, opts.PageSize)
  308. err = db.GetEngine(db.DefaultContext).Table("email_address").
  309. Select("email_address.*, `user`.name, `user`.full_name").
  310. Join("INNER", "`user`", "`user`.ID = email_address.uid").
  311. Where(cond).
  312. OrderBy(orderby).
  313. Limit(opts.PageSize, (opts.Page-1)*opts.PageSize).
  314. Find(&emails)
  315. return emails, count, err
  316. }
  317. // ActivateUserEmail will change the activated state of an email address,
  318. // either primary or secondary (all in the email_address table)
  319. func ActivateUserEmail(userID int64, email string, activate bool) (err error) {
  320. sess := db.NewSession(db.DefaultContext)
  321. defer sess.Close()
  322. if err = sess.Begin(); err != nil {
  323. return err
  324. }
  325. // Activate/deactivate a user's secondary email address
  326. // First check if there's another user active with the same address
  327. addr := EmailAddress{UID: userID, LowerEmail: strings.ToLower(email)}
  328. if has, err := sess.Get(&addr); err != nil {
  329. return err
  330. } else if !has {
  331. return fmt.Errorf("no such email: %d (%s)", userID, email)
  332. }
  333. if addr.IsActivated == activate {
  334. // Already in the desired state; no action
  335. return nil
  336. }
  337. if activate {
  338. if used, err := isEmailActive(sess, email, addr.ID); err != nil {
  339. return fmt.Errorf("unable to check isEmailActive() for %s: %v", email, err)
  340. } else if used {
  341. return ErrEmailAlreadyUsed{Email: email}
  342. }
  343. }
  344. if err = addr.updateActivation(sess, activate); err != nil {
  345. return fmt.Errorf("unable to updateActivation() for %d:%s: %w", addr.ID, addr.Email, err)
  346. }
  347. // Activate/deactivate a user's primary email address and account
  348. if addr.IsPrimary {
  349. user := User{ID: userID, Email: email}
  350. if has, err := sess.Get(&user); err != nil {
  351. return err
  352. } else if !has {
  353. return fmt.Errorf("no user with ID: %d and Email: %s", userID, email)
  354. }
  355. // The user's activation state should be synchronized with the primary email
  356. if user.IsActive != activate {
  357. user.IsActive = activate
  358. if user.Rands, err = GetUserSalt(); err != nil {
  359. return fmt.Errorf("unable to generate salt: %v", err)
  360. }
  361. if err = updateUserCols(sess, &user, "is_active", "rands"); err != nil {
  362. return fmt.Errorf("unable to updateUserCols() for user ID: %d: %v", userID, err)
  363. }
  364. }
  365. }
  366. return sess.Commit()
  367. }