alist/server/handles/ldap_login.go

160 lines
4.1 KiB
Go

package handles
import (
"crypto/tls"
"errors"
"fmt"
"strings"
"github.com/alist-org/alist/v3/internal/conf"
"github.com/alist-org/alist/v3/internal/db"
"github.com/alist-org/alist/v3/internal/model"
"github.com/alist-org/alist/v3/internal/op"
"github.com/alist-org/alist/v3/internal/setting"
"github.com/alist-org/alist/v3/pkg/utils"
"github.com/alist-org/alist/v3/pkg/utils/random"
"github.com/alist-org/alist/v3/server/common"
"github.com/gin-gonic/gin"
"gopkg.in/ldap.v3"
)
func LoginLdap(c *gin.Context) {
var req LoginReq
if err := c.ShouldBind(&req); err != nil {
common.ErrorResp(c, err, 400)
return
}
loginLdap(c, &req)
}
func loginLdap(c *gin.Context, req *LoginReq) {
enabled := setting.GetBool(conf.LdapLoginEnabled)
if !enabled {
common.ErrorStrResp(c, "ldap is not enabled", 403)
return
}
// check count of login
ip := c.ClientIP()
count, ok := loginCache.Get(ip)
if ok && count >= defaultTimes {
common.ErrorStrResp(c, "Too many unsuccessful sign-in attempts have been made using an incorrect username or password, Try again later.", 429)
loginCache.Expire(ip, defaultDuration)
return
}
// Auth start
ldapServer := setting.GetStr(conf.LdapServer)
ldapManagerDN := setting.GetStr(conf.LdapManagerDN)
ldapManagerPassword := setting.GetStr(conf.LdapManagerPassword)
ldapUserSearchBase := setting.GetStr(conf.LdapUserSearchBase)
ldapUserSearchFilter := setting.GetStr(conf.LdapUserSearchFilter) // (uid=%s)
var tlsEnabled bool = false
if strings.HasPrefix(ldapServer, "ldaps://") {
tlsEnabled = true
ldapServer = strings.TrimPrefix(ldapServer, "ldaps://")
} else if strings.HasPrefix(ldapServer, "ldap://") {
ldapServer = strings.TrimPrefix(ldapServer, "ldap://")
}
l, err := ldap.Dial("tcp", ldapServer)
if err != nil {
utils.Log.Errorf("failed to connect to LDAP: %v", err)
common.ErrorResp(c, err, 500)
return
}
defer l.Close()
if tlsEnabled {
// Reconnect with TLS
err = l.StartTLS(&tls.Config{InsecureSkipVerify: true})
if err != nil {
utils.Log.Errorf("failed to start tls: %v", err)
common.ErrorResp(c, err, 500)
return
}
}
// First bind with a read only user
if ldapManagerDN != "" && ldapManagerPassword != "" {
err = l.Bind(ldapManagerDN, ldapManagerPassword)
if err != nil {
utils.Log.Errorf("Failed to bind to LDAP: %v", err)
common.ErrorResp(c, err, 500)
return
}
}
// Search for the given username
searchRequest := ldap.NewSearchRequest(
ldapUserSearchBase,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
fmt.Sprintf(ldapUserSearchFilter, req.Username),
[]string{"dn"},
nil,
)
sr, err := l.Search(searchRequest)
if err != nil {
utils.Log.Errorf("LDAP search failed: %v", err)
common.ErrorResp(c, err, 500)
return
}
if len(sr.Entries) != 1 {
utils.Log.Errorf("User does not exist or too many entries returned")
common.ErrorResp(c, err, 500)
return
}
userDN := sr.Entries[0].DN
// Bind as the user to verify their password
err = l.Bind(userDN, req.Password)
if err != nil {
utils.Log.Errorf("Failed to auth. %v", err)
common.ErrorResp(c, err, 400)
loginCache.Set(ip, count+1)
return
} else {
utils.Log.Infof("Auth successful username:%s", req.Username)
}
// Auth finished
user, err := op.GetUserByName(req.Username)
if err != nil {
user, err = ladpRegister(req.Username)
if err != nil {
common.ErrorResp(c, err, 400)
loginCache.Set(ip, count+1)
return
}
}
// generate token
token, err := common.GenerateToken(user)
if err != nil {
common.ErrorResp(c, err, 400, true)
return
}
common.SuccessResp(c, gin.H{"token": token})
loginCache.Del(ip)
}
func ladpRegister(username string) (*model.User, error) {
if username == "" {
return nil, errors.New("cannot get username from ldap provider")
}
user := &model.User{
ID: 0,
Username: username,
Password: random.String(16),
Permission: int32(setting.GetInt(conf.LdapDefaultPermission, 0)),
BasePath: setting.GetStr(conf.LdapDefaultDir),
Role: 0,
Disabled: false,
}
if err := db.CreateUser(user); err != nil {
return nil, err
}
return user, nil
}