@@ -13,8 +13,10 @@ import (
13
13
"strings"
14
14
15
15
"code.gitea.io/gitea/models/db"
16
+ "code.gitea.io/gitea/modules/base"
16
17
"code.gitea.io/gitea/modules/log"
17
18
"code.gitea.io/gitea/modules/setting"
19
+ "code.gitea.io/gitea/modules/util"
18
20
19
21
"xorm.io/builder"
20
22
)
@@ -275,3 +277,247 @@ func DeleteInactiveEmailAddresses(ctx context.Context) error {
275
277
Delete (new (EmailAddress ))
276
278
return err
277
279
}
280
+
281
+ // ActivateEmail activates the email address to given user.
282
+ func ActivateEmail (email * EmailAddress ) error {
283
+ ctx , committer , err := db .TxContext ()
284
+ if err != nil {
285
+ return err
286
+ }
287
+ defer committer .Close ()
288
+ if err := updateActivation (db .GetEngine (ctx ), email , true ); err != nil {
289
+ return err
290
+ }
291
+ return committer .Commit ()
292
+ }
293
+
294
+ func updateActivation (e db.Engine , email * EmailAddress , activate bool ) error {
295
+ user , err := GetUserByIDEngine (e , email .UID )
296
+ if err != nil {
297
+ return err
298
+ }
299
+ if user .Rands , err = GetUserSalt (); err != nil {
300
+ return err
301
+ }
302
+ email .IsActivated = activate
303
+ if _ , err := e .ID (email .ID ).Cols ("is_activated" ).Update (email ); err != nil {
304
+ return err
305
+ }
306
+ return UpdateUserColsEngine (e , user , "rands" )
307
+ }
308
+
309
+ // MakeEmailPrimary sets primary email address of given user.
310
+ func MakeEmailPrimary (email * EmailAddress ) error {
311
+ has , err := db .GetEngine (db .DefaultContext ).Get (email )
312
+ if err != nil {
313
+ return err
314
+ } else if ! has {
315
+ return ErrEmailAddressNotExist {Email : email .Email }
316
+ }
317
+
318
+ if ! email .IsActivated {
319
+ return ErrEmailNotActivated
320
+ }
321
+
322
+ user := & User {}
323
+ has , err = db .GetEngine (db .DefaultContext ).ID (email .UID ).Get (user )
324
+ if err != nil {
325
+ return err
326
+ } else if ! has {
327
+ return ErrUserNotExist {
328
+ UID : email .UID ,
329
+ Name : "" ,
330
+ KeyID : 0 ,
331
+ }
332
+ }
333
+
334
+ ctx , committer , err := db .TxContext ()
335
+ if err != nil {
336
+ return err
337
+ }
338
+ defer committer .Close ()
339
+ sess := db .GetEngine (ctx )
340
+
341
+ // 1. Update user table
342
+ user .Email = email .Email
343
+ if _ , err = sess .ID (user .ID ).Cols ("email" ).Update (user ); err != nil {
344
+ return err
345
+ }
346
+
347
+ // 2. Update old primary email
348
+ if _ , err = sess .Where ("uid=? AND is_primary=?" , email .UID , true ).Cols ("is_primary" ).Update (& EmailAddress {
349
+ IsPrimary : false ,
350
+ }); err != nil {
351
+ return err
352
+ }
353
+
354
+ // 3. update new primary email
355
+ email .IsPrimary = true
356
+ if _ , err = sess .ID (email .ID ).Cols ("is_primary" ).Update (email ); err != nil {
357
+ return err
358
+ }
359
+
360
+ return committer .Commit ()
361
+ }
362
+
363
+ // VerifyActiveEmailCode verifies active email code when active account
364
+ func VerifyActiveEmailCode (code , email string ) * EmailAddress {
365
+ minutes := setting .Service .ActiveCodeLives
366
+
367
+ if user := GetVerifyUser (code ); user != nil {
368
+ // time limit code
369
+ prefix := code [:base .TimeLimitCodeLength ]
370
+ data := fmt .Sprintf ("%d%s%s%s%s" , user .ID , email , user .LowerName , user .Passwd , user .Rands )
371
+
372
+ if base .VerifyTimeLimitCode (data , minutes , prefix ) {
373
+ emailAddress := & EmailAddress {UID : user .ID , Email : email }
374
+ if has , _ := db .GetEngine (db .DefaultContext ).Get (emailAddress ); has {
375
+ return emailAddress
376
+ }
377
+ }
378
+ }
379
+ return nil
380
+ }
381
+
382
+ // SearchEmailOrderBy is used to sort the results from SearchEmails()
383
+ type SearchEmailOrderBy string
384
+
385
+ func (s SearchEmailOrderBy ) String () string {
386
+ return string (s )
387
+ }
388
+
389
+ // Strings for sorting result
390
+ const (
391
+ SearchEmailOrderByEmail SearchEmailOrderBy = "email_address.lower_email ASC, email_address.is_primary DESC, email_address.id ASC"
392
+ SearchEmailOrderByEmailReverse SearchEmailOrderBy = "email_address.lower_email DESC, email_address.is_primary ASC, email_address.id DESC"
393
+ SearchEmailOrderByName SearchEmailOrderBy = "`user`.lower_name ASC, email_address.is_primary DESC, email_address.id ASC"
394
+ SearchEmailOrderByNameReverse SearchEmailOrderBy = "`user`.lower_name DESC, email_address.is_primary ASC, email_address.id DESC"
395
+ )
396
+
397
+ // SearchEmailOptions are options to search e-mail addresses for the admin panel
398
+ type SearchEmailOptions struct {
399
+ db.ListOptions
400
+ Keyword string
401
+ SortType SearchEmailOrderBy
402
+ IsPrimary util.OptionalBool
403
+ IsActivated util.OptionalBool
404
+ }
405
+
406
+ // SearchEmailResult is an e-mail address found in the user or email_address table
407
+ type SearchEmailResult struct {
408
+ UID int64
409
+ Email string
410
+ IsActivated bool
411
+ IsPrimary bool
412
+ // From User
413
+ Name string
414
+ FullName string
415
+ }
416
+
417
+ // SearchEmails takes options i.e. keyword and part of email name to search,
418
+ // it returns results in given range and number of total results.
419
+ func SearchEmails (opts * SearchEmailOptions ) ([]* SearchEmailResult , int64 , error ) {
420
+ var cond builder.Cond = builder.Eq {"`user`.`type`" : UserTypeIndividual }
421
+ if len (opts .Keyword ) > 0 {
422
+ likeStr := "%" + strings .ToLower (opts .Keyword ) + "%"
423
+ cond = cond .And (builder .Or (
424
+ builder.Like {"lower(`user`.full_name)" , likeStr },
425
+ builder.Like {"`user`.lower_name" , likeStr },
426
+ builder.Like {"email_address.lower_email" , likeStr },
427
+ ))
428
+ }
429
+
430
+ switch {
431
+ case opts .IsPrimary .IsTrue ():
432
+ cond = cond .And (builder.Eq {"email_address.is_primary" : true })
433
+ case opts .IsPrimary .IsFalse ():
434
+ cond = cond .And (builder.Eq {"email_address.is_primary" : false })
435
+ }
436
+
437
+ switch {
438
+ case opts .IsActivated .IsTrue ():
439
+ cond = cond .And (builder.Eq {"email_address.is_activated" : true })
440
+ case opts .IsActivated .IsFalse ():
441
+ cond = cond .And (builder.Eq {"email_address.is_activated" : false })
442
+ }
443
+
444
+ count , err := db .GetEngine (db .DefaultContext ).Join ("INNER" , "`user`" , "`user`.ID = email_address.uid" ).
445
+ Where (cond ).Count (new (EmailAddress ))
446
+ if err != nil {
447
+ return nil , 0 , fmt .Errorf ("Count: %v" , err )
448
+ }
449
+
450
+ orderby := opts .SortType .String ()
451
+ if orderby == "" {
452
+ orderby = SearchEmailOrderByEmail .String ()
453
+ }
454
+
455
+ opts .SetDefaultValues ()
456
+
457
+ emails := make ([]* SearchEmailResult , 0 , opts .PageSize )
458
+ err = db .GetEngine (db .DefaultContext ).Table ("email_address" ).
459
+ Select ("email_address.*, `user`.name, `user`.full_name" ).
460
+ Join ("INNER" , "`user`" , "`user`.ID = email_address.uid" ).
461
+ Where (cond ).
462
+ OrderBy (orderby ).
463
+ Limit (opts .PageSize , (opts .Page - 1 )* opts .PageSize ).
464
+ Find (& emails )
465
+
466
+ return emails , count , err
467
+ }
468
+
469
+ // ActivateUserEmail will change the activated state of an email address,
470
+ // either primary or secondary (all in the email_address table)
471
+ func ActivateUserEmail (userID int64 , email string , activate bool ) (err error ) {
472
+ ctx , committer , err := db .TxContext ()
473
+ if err != nil {
474
+ return err
475
+ }
476
+ defer committer .Close ()
477
+ sess := db .GetEngine (ctx )
478
+
479
+ // Activate/deactivate a user's secondary email address
480
+ // First check if there's another user active with the same address
481
+ addr := EmailAddress {UID : userID , LowerEmail : strings .ToLower (email )}
482
+ if has , err := sess .Get (& addr ); err != nil {
483
+ return err
484
+ } else if ! has {
485
+ return fmt .Errorf ("no such email: %d (%s)" , userID , email )
486
+ }
487
+ if addr .IsActivated == activate {
488
+ // Already in the desired state; no action
489
+ return nil
490
+ }
491
+ if activate {
492
+ if used , err := IsEmailActive (ctx , email , addr .ID ); err != nil {
493
+ return fmt .Errorf ("unable to check isEmailActive() for %s: %v" , email , err )
494
+ } else if used {
495
+ return ErrEmailAlreadyUsed {Email : email }
496
+ }
497
+ }
498
+ if err = updateActivation (sess , & addr , activate ); err != nil {
499
+ return fmt .Errorf ("unable to updateActivation() for %d:%s: %w" , addr .ID , addr .Email , err )
500
+ }
501
+
502
+ // Activate/deactivate a user's primary email address and account
503
+ if addr .IsPrimary {
504
+ user := User {ID : userID , Email : email }
505
+ if has , err := sess .Get (& user ); err != nil {
506
+ return err
507
+ } else if ! has {
508
+ return fmt .Errorf ("no user with ID: %d and Email: %s" , userID , email )
509
+ }
510
+ // The user's activation state should be synchronized with the primary email
511
+ if user .IsActive != activate {
512
+ user .IsActive = activate
513
+ if user .Rands , err = GetUserSalt (); err != nil {
514
+ return fmt .Errorf ("unable to generate salt: %v" , err )
515
+ }
516
+ if err = UpdateUserColsEngine (sess , & user , "is_active" , "rands" ); err != nil {
517
+ return fmt .Errorf ("unable to updateUserCols() for user ID: %d: %v" , userID , err )
518
+ }
519
+ }
520
+ }
521
+
522
+ return committer .Commit ()
523
+ }
0 commit comments