2021-07-24 11:16:34 +01:00
// Copyright 2021 The Gitea Authors. All rights reserved.
2022-11-27 13:20:29 -05:00
// SPDX-License-Identifier: MIT
2021-07-24 11:16:34 +01:00
package smtp
import (
2023-09-14 19:09:32 +02:00
"context"
2021-07-24 11:16:34 +01:00
"errors"
"net/smtp"
"net/textproto"
"strings"
2022-01-02 21:12:35 +08:00
auth_model "code.gitea.io/gitea/models/auth"
2021-11-24 17:49:20 +08:00
user_model "code.gitea.io/gitea/models/user"
2021-07-24 11:16:34 +01:00
"code.gitea.io/gitea/modules/util"
)
// Authenticate queries if the provided login/password is authenticates against the SMTP server
// Users will be autoregistered as required
2023-09-14 19:09:32 +02:00
func ( source * Source ) Authenticate ( ctx context . Context , user * user_model . User , userName , password string ) ( * user_model . User , error ) {
2021-07-24 11:16:34 +01:00
// Verify allowed domains.
if len ( source . AllowedDomains ) > 0 {
2021-09-24 19:32:56 +08:00
idx := strings . Index ( userName , "@" )
2021-07-24 11:16:34 +01:00
if idx == - 1 {
2021-11-24 17:49:20 +08:00
return nil , user_model . ErrUserNotExist { Name : userName }
Improve utils of slices (#22379)
- Move the file `compare.go` and `slice.go` to `slice.go`.
- Fix `ExistsInSlice`, it's buggy
- It uses `sort.Search`, so it assumes that the input slice is sorted.
- It passes `func(i int) bool { return slice[i] == target })` to
`sort.Search`, that's incorrect, check the doc of `sort.Search`.
- Conbine `IsInt64InSlice(int64, []int64)` and `ExistsInSlice(string,
[]string)` to `SliceContains[T]([]T, T)`.
- Conbine `IsSliceInt64Eq([]int64, []int64)` and `IsEqualSlice([]string,
[]string)` to `SliceSortedEqual[T]([]T, T)`.
- Add `SliceEqual[T]([]T, T)` as a distinction from
`SliceSortedEqual[T]([]T, T)`.
- Redesign `RemoveIDFromList([]int64, int64) ([]int64, bool)` to
`SliceRemoveAll[T]([]T, T) []T`.
- Add `SliceContainsFunc[T]([]T, func(T) bool)` and
`SliceRemoveAllFunc[T]([]T, func(T) bool)` for general use.
- Add comments to explain why not `golang.org/x/exp/slices`.
- Add unit tests.
2023-01-11 13:31:16 +08:00
} else if ! util . SliceContainsString ( strings . Split ( source . AllowedDomains , "," ) , userName [ idx + 1 : ] , true ) {
2021-11-24 17:49:20 +08:00
return nil , user_model . ErrUserNotExist { Name : userName }
2021-07-24 11:16:34 +01:00
}
}
var auth smtp . Auth
2021-08-11 21:42:58 +01:00
switch source . Auth {
case PlainAuthentication :
2022-11-11 05:12:23 +08:00
auth = smtp . PlainAuth ( "" , userName , password , source . Host )
2021-08-11 21:42:58 +01:00
case LoginAuthentication :
2021-09-24 19:32:56 +08:00
auth = & loginAuthenticator { userName , password }
2021-08-11 21:42:58 +01:00
case CRAMMD5Authentication :
2021-09-24 19:32:56 +08:00
auth = smtp . CRAMMD5Auth ( userName , password )
2021-08-11 21:42:58 +01:00
default :
return nil , errors . New ( "unsupported SMTP auth type" )
2021-07-24 11:16:34 +01:00
}
if err := Authenticate ( auth , source ) ; err != nil {
// Check standard error format first,
// then fallback to worse case.
tperr , ok := err . ( * textproto . Error )
if ( ok && tperr . Code == 535 ) ||
strings . Contains ( err . Error ( ) , "Username and Password not accepted" ) {
2021-11-24 17:49:20 +08:00
return nil , user_model . ErrUserNotExist { Name : userName }
2021-07-24 11:16:34 +01:00
}
2021-08-11 21:42:58 +01:00
if ( ok && tperr . Code == 534 ) ||
strings . Contains ( err . Error ( ) , "Application-specific password required" ) {
2021-11-24 17:49:20 +08:00
return nil , user_model . ErrUserNotExist { Name : userName }
2021-08-11 21:42:58 +01:00
}
2021-07-24 11:16:34 +01:00
return nil , err
}
if user != nil {
return user , nil
}
2021-09-24 19:32:56 +08:00
username := userName
idx := strings . Index ( userName , "@" )
2021-07-24 11:16:34 +01:00
if idx > - 1 {
2021-09-24 19:32:56 +08:00
username = userName [ : idx ]
2021-07-24 11:16:34 +01:00
}
2021-11-24 17:49:20 +08:00
user = & user_model . User {
2021-07-24 11:16:34 +01:00
LowerName : strings . ToLower ( username ) ,
Name : strings . ToLower ( username ) ,
2021-09-24 19:32:56 +08:00
Email : userName ,
2021-07-24 11:16:34 +01:00
Passwd : password ,
2022-01-02 21:12:35 +08:00
LoginType : auth_model . SMTP ,
LoginSource : source . authSource . ID ,
2021-09-24 19:32:56 +08:00
LoginName : userName ,
2022-04-29 21:38:11 +02:00
}
overwriteDefault := & user_model . CreateUserOverwriteOptions {
IsActive : util . OptionalBoolTrue ,
2021-07-24 11:16:34 +01:00
}
2021-08-12 08:26:33 +01:00
2023-09-14 19:09:32 +02:00
if err := user_model . CreateUser ( ctx , user , overwriteDefault ) ; err != nil {
2021-08-12 08:26:33 +01:00
return user , err
}
return user , nil
2021-07-24 11:16:34 +01:00
}
2021-09-27 02:02:01 +01:00
// IsSkipLocalTwoFA returns if this source should skip local 2fa for password authentication
func ( source * Source ) IsSkipLocalTwoFA ( ) bool {
return source . SkipLocalTwoFA
}