2021-11-11 10:03:30 +03:00
// Copyright 2021 The Gitea Authors. All rights reserved.
2016-07-16 05:08:04 +03:00
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package models
import (
"fmt"
"strings"
2020-03-02 21:25:36 +03:00
2021-09-19 14:49:59 +03:00
"code.gitea.io/gitea/models/db"
2021-11-11 10:03:30 +03:00
user_model "code.gitea.io/gitea/models/user"
2020-03-02 21:25:36 +03:00
"code.gitea.io/gitea/modules/util"
2021-11-17 15:34:35 +03:00
2020-03-02 21:25:36 +03:00
"xorm.io/builder"
2016-07-16 05:08:04 +03:00
)
2021-11-11 10:03:30 +03:00
// ActivateEmail activates the email address to given user.
func ActivateEmail ( email * user_model . EmailAddress ) error {
2021-11-21 18:41:00 +03:00
ctx , committer , err := db . TxContext ( )
if err != nil {
2016-07-16 05:08:04 +03:00
return err
}
2021-11-21 18:41:00 +03:00
defer committer . Close ( )
if err := updateActivation ( db . GetEngine ( ctx ) , email , true ) ; err != nil {
2016-12-20 15:32:02 +03:00
return err
}
2021-11-21 18:41:00 +03:00
return committer . Commit ( )
2020-03-02 21:25:36 +03:00
}
2016-07-16 05:08:04 +03:00
2021-11-11 10:03:30 +03:00
func updateActivation ( e db . Engine , email * user_model . EmailAddress , activate bool ) error {
2020-03-02 21:25:36 +03:00
user , err := getUserByID ( e , email . UID )
if err != nil {
2016-07-16 05:08:04 +03:00
return err
}
2020-03-02 21:25:36 +03:00
if user . Rands , err = GetUserSalt ( ) ; err != nil {
2016-07-16 05:08:04 +03:00
return err
2020-03-02 21:25:36 +03:00
}
email . IsActivated = activate
if _ , err := e . ID ( email . ID ) . Cols ( "is_activated" ) . Update ( email ) ; err != nil {
2016-07-16 05:08:04 +03:00
return err
}
2020-03-02 21:25:36 +03:00
return updateUserCols ( e , user , "rands" )
2016-07-16 05:08:04 +03:00
}
2016-11-27 14:59:12 +03:00
// MakeEmailPrimary sets primary email address of given user.
2021-11-11 10:03:30 +03:00
func MakeEmailPrimary ( email * user_model . EmailAddress ) error {
2021-09-23 18:45:36 +03:00
has , err := db . GetEngine ( db . DefaultContext ) . Get ( email )
2016-07-16 05:08:04 +03:00
if err != nil {
return err
} else if ! has {
2021-11-11 10:03:30 +03:00
return user_model . ErrEmailAddressNotExist { Email : email . Email }
2016-07-16 05:08:04 +03:00
}
if ! email . IsActivated {
2021-11-11 10:03:30 +03:00
return user_model . ErrEmailNotActivated
2016-07-16 05:08:04 +03:00
}
2020-06-17 20:50:11 +03:00
user := & User { }
2021-09-23 18:45:36 +03:00
has , err = db . GetEngine ( db . DefaultContext ) . ID ( email . UID ) . Get ( user )
2016-07-16 05:08:04 +03:00
if err != nil {
return err
} else if ! has {
2016-11-12 02:01:09 +03:00
return ErrUserNotExist { email . UID , "" , 0 }
2016-07-16 05:08:04 +03:00
}
2021-11-21 18:41:00 +03:00
ctx , committer , err := db . TxContext ( )
if err != nil {
2016-07-16 05:08:04 +03:00
return err
}
2021-11-21 18:41:00 +03:00
defer committer . Close ( )
sess := db . GetEngine ( ctx )
2016-07-16 05:08:04 +03:00
2021-06-08 06:52:51 +03:00
// 1. Update user table
2016-07-16 05:08:04 +03:00
user . Email = email . Email
2017-10-05 07:43:04 +03:00
if _ , err = sess . ID ( user . ID ) . Cols ( "email" ) . Update ( user ) ; err != nil {
2016-07-16 05:08:04 +03:00
return err
}
2021-06-08 06:52:51 +03:00
// 2. Update old primary email
2021-11-11 10:03:30 +03:00
if _ , err = sess . Where ( "uid=? AND is_primary=?" , email . UID , true ) . Cols ( "is_primary" ) . Update ( & user_model . EmailAddress {
2021-06-08 06:52:51 +03:00
IsPrimary : false ,
} ) ; err != nil {
return err
}
2021-07-08 14:38:13 +03:00
// 3. update new primary email
2021-06-08 06:52:51 +03:00
email . IsPrimary = true
if _ , err = sess . ID ( email . ID ) . Cols ( "is_primary" ) . Update ( email ) ; err != nil {
return err
}
2021-11-21 18:41:00 +03:00
return committer . Commit ( )
2016-07-16 05:08:04 +03:00
}
2020-03-02 21:25:36 +03:00
// SearchEmailOrderBy is used to sort the results from SearchEmails()
type SearchEmailOrderBy string
func ( s SearchEmailOrderBy ) String ( ) string {
return string ( s )
}
// Strings for sorting result
const (
2021-06-08 06:52:51 +03:00
SearchEmailOrderByEmail SearchEmailOrderBy = "email_address.lower_email ASC, email_address.is_primary DESC, email_address.id ASC"
SearchEmailOrderByEmailReverse SearchEmailOrderBy = "email_address.lower_email DESC, email_address.is_primary ASC, email_address.id DESC"
SearchEmailOrderByName SearchEmailOrderBy = "`user`.lower_name ASC, email_address.is_primary DESC, email_address.id ASC"
SearchEmailOrderByNameReverse SearchEmailOrderBy = "`user`.lower_name DESC, email_address.is_primary ASC, email_address.id DESC"
2020-03-02 21:25:36 +03:00
)
// SearchEmailOptions are options to search e-mail addresses for the admin panel
type SearchEmailOptions struct {
2021-09-24 14:32:56 +03:00
db . ListOptions
2020-03-02 21:25:36 +03:00
Keyword string
SortType SearchEmailOrderBy
IsPrimary util . OptionalBool
IsActivated util . OptionalBool
}
// SearchEmailResult is an e-mail address found in the user or email_address table
type SearchEmailResult struct {
UID int64
Email string
IsActivated bool
IsPrimary bool
// From User
Name string
FullName string
}
// SearchEmails takes options i.e. keyword and part of email name to search,
// it returns results in given range and number of total results.
func SearchEmails ( opts * SearchEmailOptions ) ( [ ] * SearchEmailResult , int64 , error ) {
2021-06-30 19:37:20 +03:00
var cond builder . Cond = builder . Eq { "`user`.`type`" : UserTypeIndividual }
2020-03-02 21:25:36 +03:00
if len ( opts . Keyword ) > 0 {
likeStr := "%" + strings . ToLower ( opts . Keyword ) + "%"
2021-06-08 06:52:51 +03:00
cond = cond . And ( builder . Or (
builder . Like { "lower(`user`.full_name)" , likeStr } ,
builder . Like { "`user`.lower_name" , likeStr } ,
builder . Like { "email_address.lower_email" , likeStr } ,
) )
2020-03-02 21:25:36 +03:00
}
switch {
case opts . IsPrimary . IsTrue ( ) :
2021-06-08 06:52:51 +03:00
cond = cond . And ( builder . Eq { "email_address.is_primary" : true } )
2020-03-02 21:25:36 +03:00
case opts . IsPrimary . IsFalse ( ) :
2021-06-08 06:52:51 +03:00
cond = cond . And ( builder . Eq { "email_address.is_primary" : false } )
2020-03-02 21:25:36 +03:00
}
switch {
case opts . IsActivated . IsTrue ( ) :
2021-06-08 06:52:51 +03:00
cond = cond . And ( builder . Eq { "email_address.is_activated" : true } )
2020-03-02 21:25:36 +03:00
case opts . IsActivated . IsFalse ( ) :
2021-06-08 06:52:51 +03:00
cond = cond . And ( builder . Eq { "email_address.is_activated" : false } )
2020-03-02 21:25:36 +03:00
}
2021-09-23 18:45:36 +03:00
count , err := db . GetEngine ( db . DefaultContext ) . Join ( "INNER" , "`user`" , "`user`.ID = email_address.uid" ) .
2021-11-11 10:03:30 +03:00
Where ( cond ) . Count ( new ( user_model . EmailAddress ) )
2020-03-02 21:25:36 +03:00
if err != nil {
return nil , 0 , fmt . Errorf ( "Count: %v" , err )
}
orderby := opts . SortType . String ( )
if orderby == "" {
orderby = SearchEmailOrderByEmail . String ( )
}
2021-09-24 14:32:56 +03:00
opts . SetDefaultValues ( )
2020-03-02 21:25:36 +03:00
emails := make ( [ ] * SearchEmailResult , 0 , opts . PageSize )
2021-09-23 18:45:36 +03:00
err = db . GetEngine ( db . DefaultContext ) . Table ( "email_address" ) .
2021-06-08 06:52:51 +03:00
Select ( "email_address.*, `user`.name, `user`.full_name" ) .
Join ( "INNER" , "`user`" , "`user`.ID = email_address.uid" ) .
Where ( cond ) .
OrderBy ( orderby ) .
Limit ( opts . PageSize , ( opts . Page - 1 ) * opts . PageSize ) .
Find ( & emails )
2020-03-02 21:25:36 +03:00
return emails , count , err
}
// ActivateUserEmail will change the activated state of an email address,
2021-07-13 23:59:27 +03:00
// either primary or secondary (all in the email_address table)
func ActivateUserEmail ( userID int64 , email string , activate bool ) ( err error ) {
2021-11-11 10:03:30 +03:00
ctx , committer , err := db . TxContext ( )
if err != nil {
2020-03-02 21:25:36 +03:00
return err
}
2021-11-11 10:03:30 +03:00
defer committer . Close ( )
sess := db . GetEngine ( ctx )
2021-06-08 06:52:51 +03:00
// Activate/deactivate a user's secondary email address
// First check if there's another user active with the same address
2021-11-11 10:03:30 +03:00
addr := user_model . EmailAddress { UID : userID , LowerEmail : strings . ToLower ( email ) }
2021-06-08 06:52:51 +03:00
if has , err := sess . Get ( & addr ) ; err != nil {
return err
} else if ! has {
return fmt . Errorf ( "no such email: %d (%s)" , userID , email )
}
if addr . IsActivated == activate {
// Already in the desired state; no action
return nil
}
if activate {
2021-11-11 10:03:30 +03:00
if used , err := user_model . IsEmailActive ( ctx , email , addr . ID ) ; err != nil {
2021-07-13 23:59:27 +03:00
return fmt . Errorf ( "unable to check isEmailActive() for %s: %v" , email , err )
2021-06-08 06:52:51 +03:00
} else if used {
2021-11-11 10:03:30 +03:00
return user_model . ErrEmailAlreadyUsed { Email : email }
2021-06-08 06:52:51 +03:00
}
}
2021-11-11 10:03:30 +03:00
if err = updateActivation ( sess , & addr , activate ) ; err != nil {
2021-07-13 23:59:27 +03:00
return fmt . Errorf ( "unable to updateActivation() for %d:%s: %w" , addr . ID , addr . Email , err )
2021-06-08 06:52:51 +03:00
}
2021-07-13 23:59:27 +03:00
// Activate/deactivate a user's primary email address and account
if addr . IsPrimary {
2020-03-02 21:25:36 +03:00
user := User { ID : userID , Email : email }
if has , err := sess . Get ( & user ) ; err != nil {
return err
} else if ! has {
2021-07-13 23:59:27 +03:00
return fmt . Errorf ( "no user with ID: %d and Email: %s" , userID , email )
2020-03-02 21:25:36 +03:00
}
2021-07-13 23:59:27 +03:00
// The user's activation state should be synchronized with the primary email
if user . IsActive != activate {
user . IsActive = activate
if user . Rands , err = GetUserSalt ( ) ; err != nil {
return fmt . Errorf ( "unable to generate salt: %v" , err )
}
if err = updateUserCols ( sess , & user , "is_active" , "rands" ) ; err != nil {
return fmt . Errorf ( "unable to updateUserCols() for user ID: %d: %v" , userID , err )
}
2020-03-02 21:25:36 +03:00
}
}
2021-06-08 06:52:51 +03:00
2021-11-11 10:03:30 +03:00
return committer . Commit ( )
2020-03-02 21:25:36 +03:00
}