gofmt
Some checks failed
Release 3X-UI / build (386) (push) Has been cancelled
Release 3X-UI / build (amd64) (push) Has been cancelled
Release 3X-UI / build (arm64) (push) Has been cancelled
Release 3X-UI / build (armv5) (push) Has been cancelled
Release 3X-UI / build (armv6) (push) Has been cancelled
Release 3X-UI / build (armv7) (push) Has been cancelled
Release 3X-UI / build (s390x) (push) Has been cancelled
Release 3X-UI / Build for Windows (push) Has been cancelled

This commit is contained in:
mhsanaei 2025-10-21 13:02:55 +02:00
parent 01d4a7488d
commit 713a7328f6
No known key found for this signature in database
GPG key ID: D875CD086CF668A0
5 changed files with 223 additions and 226 deletions

View file

@ -1,144 +1,142 @@
package ldaputil package ldaputil
import ( import (
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"github.com/go-ldap/ldap/v3" "github.com/go-ldap/ldap/v3"
) )
type Config struct { type Config struct {
Host string Host string
Port int Port int
UseTLS bool UseTLS bool
BindDN string BindDN string
Password string Password string
BaseDN string BaseDN string
UserFilter string UserFilter string
UserAttr string UserAttr string
FlagField string FlagField string
TruthyVals []string TruthyVals []string
Invert bool Invert bool
} }
// FetchVlessFlags returns map[email]enabled // FetchVlessFlags returns map[email]enabled
func FetchVlessFlags(cfg Config) (map[string]bool, error) { func FetchVlessFlags(cfg Config) (map[string]bool, error) {
addr := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port) addr := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port)
var conn *ldap.Conn var conn *ldap.Conn
var err error var err error
if cfg.UseTLS { if cfg.UseTLS {
conn, err = ldap.DialTLS("tcp", addr, &tls.Config{InsecureSkipVerify: false}) conn, err = ldap.DialTLS("tcp", addr, &tls.Config{InsecureSkipVerify: false})
} else { } else {
conn, err = ldap.Dial("tcp", addr) conn, err = ldap.Dial("tcp", addr)
} }
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer conn.Close() defer conn.Close()
if cfg.BindDN != "" { if cfg.BindDN != "" {
if err := conn.Bind(cfg.BindDN, cfg.Password); err != nil { if err := conn.Bind(cfg.BindDN, cfg.Password); err != nil {
return nil, err return nil, err
} }
} }
if cfg.UserFilter == "" { if cfg.UserFilter == "" {
cfg.UserFilter = "(objectClass=person)" cfg.UserFilter = "(objectClass=person)"
} }
if cfg.UserAttr == "" { if cfg.UserAttr == "" {
cfg.UserAttr = "mail" cfg.UserAttr = "mail"
} }
// if field not set we fallback to legacy vless_enabled // if field not set we fallback to legacy vless_enabled
if cfg.FlagField == "" { if cfg.FlagField == "" {
cfg.FlagField = "vless_enabled" cfg.FlagField = "vless_enabled"
} }
req := ldap.NewSearchRequest( req := ldap.NewSearchRequest(
cfg.BaseDN, cfg.BaseDN,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
cfg.UserFilter, cfg.UserFilter,
[]string{cfg.UserAttr, cfg.FlagField}, []string{cfg.UserAttr, cfg.FlagField},
nil, nil,
) )
res, err := conn.Search(req) res, err := conn.Search(req)
if err != nil { if err != nil {
return nil, err return nil, err
} }
result := make(map[string]bool, len(res.Entries)) result := make(map[string]bool, len(res.Entries))
for _, e := range res.Entries { for _, e := range res.Entries {
user := e.GetAttributeValue(cfg.UserAttr) user := e.GetAttributeValue(cfg.UserAttr)
if user == "" { if user == "" {
continue continue
} }
val := e.GetAttributeValue(cfg.FlagField) val := e.GetAttributeValue(cfg.FlagField)
enabled := false enabled := false
for _, t := range cfg.TruthyVals { for _, t := range cfg.TruthyVals {
if val == t { if val == t {
enabled = true enabled = true
break break
} }
} }
if cfg.Invert { if cfg.Invert {
enabled = !enabled enabled = !enabled
} }
result[user] = enabled result[user] = enabled
} }
return result, nil return result, nil
} }
// AuthenticateUser searches user by cfg.UserAttr and attempts to bind with provided password. // AuthenticateUser searches user by cfg.UserAttr and attempts to bind with provided password.
func AuthenticateUser(cfg Config, username, password string) (bool, error) { func AuthenticateUser(cfg Config, username, password string) (bool, error) {
addr := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port) addr := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port)
var conn *ldap.Conn var conn *ldap.Conn
var err error var err error
if cfg.UseTLS { if cfg.UseTLS {
conn, err = ldap.DialTLS("tcp", addr, &tls.Config{InsecureSkipVerify: false}) conn, err = ldap.DialTLS("tcp", addr, &tls.Config{InsecureSkipVerify: false})
} else { } else {
conn, err = ldap.Dial("tcp", addr) conn, err = ldap.Dial("tcp", addr)
} }
if err != nil { if err != nil {
return false, err return false, err
} }
defer conn.Close() defer conn.Close()
// Optional initial bind for search // Optional initial bind for search
if cfg.BindDN != "" { if cfg.BindDN != "" {
if err := conn.Bind(cfg.BindDN, cfg.Password); err != nil { if err := conn.Bind(cfg.BindDN, cfg.Password); err != nil {
return false, err return false, err
} }
} }
if cfg.UserFilter == "" { if cfg.UserFilter == "" {
cfg.UserFilter = "(objectClass=person)" cfg.UserFilter = "(objectClass=person)"
} }
if cfg.UserAttr == "" { if cfg.UserAttr == "" {
cfg.UserAttr = "uid" cfg.UserAttr = "uid"
} }
// Build filter to find specific user // Build filter to find specific user
filter := fmt.Sprintf("(&%s(%s=%s))", cfg.UserFilter, cfg.UserAttr, ldap.EscapeFilter(username)) filter := fmt.Sprintf("(&%s(%s=%s))", cfg.UserFilter, cfg.UserAttr, ldap.EscapeFilter(username))
req := ldap.NewSearchRequest( req := ldap.NewSearchRequest(
cfg.BaseDN, cfg.BaseDN,
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 1, 0, false, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 1, 0, false,
filter, filter,
[]string{"dn"}, []string{"dn"},
nil, nil,
) )
res, err := conn.Search(req) res, err := conn.Search(req)
if err != nil { if err != nil {
return false, err return false, err
} }
if len(res.Entries) == 0 { if len(res.Entries) == 0 {
return false, nil return false, nil
} }
userDN := res.Entries[0].DN userDN := res.Entries[0].DN
// Try to bind as the user // Try to bind as the user
if err := conn.Bind(userDN, password); err != nil { if err := conn.Bind(userDN, password); err != nil {
return false, nil return false, nil
} }
return true, nil return true, nil
} }

View file

@ -74,30 +74,30 @@ type AllSetting struct {
SubJsonFragment string `json:"subJsonFragment" form:"subJsonFragment"` // JSON subscription fragment configuration SubJsonFragment string `json:"subJsonFragment" form:"subJsonFragment"` // JSON subscription fragment configuration
SubJsonNoises string `json:"subJsonNoises" form:"subJsonNoises"` // JSON subscription noise configuration SubJsonNoises string `json:"subJsonNoises" form:"subJsonNoises"` // JSON subscription noise configuration
SubJsonMux string `json:"subJsonMux" form:"subJsonMux"` // JSON subscription mux configuration SubJsonMux string `json:"subJsonMux" form:"subJsonMux"` // JSON subscription mux configuration
SubJsonRules string `json:"subJsonRules" form:"subJsonRules"` SubJsonRules string `json:"subJsonRules" form:"subJsonRules"`
// LDAP settings // LDAP settings
LdapEnable bool `json:"ldapEnable" form:"ldapEnable"` LdapEnable bool `json:"ldapEnable" form:"ldapEnable"`
LdapHost string `json:"ldapHost" form:"ldapHost"` LdapHost string `json:"ldapHost" form:"ldapHost"`
LdapPort int `json:"ldapPort" form:"ldapPort"` LdapPort int `json:"ldapPort" form:"ldapPort"`
LdapUseTLS bool `json:"ldapUseTLS" form:"ldapUseTLS"` LdapUseTLS bool `json:"ldapUseTLS" form:"ldapUseTLS"`
LdapBindDN string `json:"ldapBindDN" form:"ldapBindDN"` LdapBindDN string `json:"ldapBindDN" form:"ldapBindDN"`
LdapPassword string `json:"ldapPassword" form:"ldapPassword"` LdapPassword string `json:"ldapPassword" form:"ldapPassword"`
LdapBaseDN string `json:"ldapBaseDN" form:"ldapBaseDN"` LdapBaseDN string `json:"ldapBaseDN" form:"ldapBaseDN"`
LdapUserFilter string `json:"ldapUserFilter" form:"ldapUserFilter"` LdapUserFilter string `json:"ldapUserFilter" form:"ldapUserFilter"`
LdapUserAttr string `json:"ldapUserAttr" form:"ldapUserAttr"` // e.g., mail or uid LdapUserAttr string `json:"ldapUserAttr" form:"ldapUserAttr"` // e.g., mail or uid
LdapVlessField string `json:"ldapVlessField" form:"ldapVlessField"` LdapVlessField string `json:"ldapVlessField" form:"ldapVlessField"`
LdapSyncCron string `json:"ldapSyncCron" form:"ldapSyncCron"` LdapSyncCron string `json:"ldapSyncCron" form:"ldapSyncCron"`
// Generic flag configuration // Generic flag configuration
LdapFlagField string `json:"ldapFlagField" form:"ldapFlagField"` LdapFlagField string `json:"ldapFlagField" form:"ldapFlagField"`
LdapTruthyValues string `json:"ldapTruthyValues" form:"ldapTruthyValues"` LdapTruthyValues string `json:"ldapTruthyValues" form:"ldapTruthyValues"`
LdapInvertFlag bool `json:"ldapInvertFlag" form:"ldapInvertFlag"` LdapInvertFlag bool `json:"ldapInvertFlag" form:"ldapInvertFlag"`
LdapInboundTags string `json:"ldapInboundTags" form:"ldapInboundTags"` LdapInboundTags string `json:"ldapInboundTags" form:"ldapInboundTags"`
LdapAutoCreate bool `json:"ldapAutoCreate" form:"ldapAutoCreate"` LdapAutoCreate bool `json:"ldapAutoCreate" form:"ldapAutoCreate"`
LdapAutoDelete bool `json:"ldapAutoDelete" form:"ldapAutoDelete"` LdapAutoDelete bool `json:"ldapAutoDelete" form:"ldapAutoDelete"`
LdapDefaultTotalGB int `json:"ldapDefaultTotalGB" form:"ldapDefaultTotalGB"` LdapDefaultTotalGB int `json:"ldapDefaultTotalGB" form:"ldapDefaultTotalGB"`
LdapDefaultExpiryDays int `json:"ldapDefaultExpiryDays" form:"ldapDefaultExpiryDays"` LdapDefaultExpiryDays int `json:"ldapDefaultExpiryDays" form:"ldapDefaultExpiryDays"`
LdapDefaultLimitIP int `json:"ldapDefaultLimitIP" form:"ldapDefaultLimitIP"` LdapDefaultLimitIP int `json:"ldapDefaultLimitIP" form:"ldapDefaultLimitIP"`
// JSON subscription routing rules // JSON subscription routing rules
} }

View file

@ -1569,21 +1569,20 @@ func (s *InboundService) ToggleClientEnableByEmail(clientEmail string) (bool, bo
return !clientOldEnabled, needRestart, nil return !clientOldEnabled, needRestart, nil
} }
// SetClientEnableByEmail sets client enable state to desired value; returns (changed, needRestart, error) // SetClientEnableByEmail sets client enable state to desired value; returns (changed, needRestart, error)
func (s *InboundService) SetClientEnableByEmail(clientEmail string, enable bool) (bool, bool, error) { func (s *InboundService) SetClientEnableByEmail(clientEmail string, enable bool) (bool, bool, error) {
current, err := s.checkIsEnabledByEmail(clientEmail) current, err := s.checkIsEnabledByEmail(clientEmail)
if err != nil { if err != nil {
return false, false, err return false, false, err
} }
if current == enable { if current == enable {
return false, false, nil return false, false, nil
} }
newEnabled, needRestart, err := s.ToggleClientEnableByEmail(clientEmail) newEnabled, needRestart, err := s.ToggleClientEnableByEmail(clientEmail)
if err != nil { if err != nil {
return false, needRestart, err return false, needRestart, err
} }
return newEnabled == enable, needRestart, nil return newEnabled == enable, needRestart, nil
} }
func (s *InboundService) ResetClientIpLimitByEmail(clientEmail string, count int) (bool, error) { func (s *InboundService) ResetClientIpLimitByEmail(clientEmail string, count int) (bool, error) {

View file

@ -74,26 +74,26 @@ var defaultValueMap = map[string]string{
"externalTrafficInformEnable": "false", "externalTrafficInformEnable": "false",
"externalTrafficInformURI": "", "externalTrafficInformURI": "",
// LDAP defaults // LDAP defaults
"ldapEnable": "false", "ldapEnable": "false",
"ldapHost": "", "ldapHost": "",
"ldapPort": "389", "ldapPort": "389",
"ldapUseTLS": "false", "ldapUseTLS": "false",
"ldapBindDN": "", "ldapBindDN": "",
"ldapPassword": "", "ldapPassword": "",
"ldapBaseDN": "", "ldapBaseDN": "",
"ldapUserFilter": "(objectClass=person)", "ldapUserFilter": "(objectClass=person)",
"ldapUserAttr": "mail", "ldapUserAttr": "mail",
"ldapVlessField": "vless_enabled", "ldapVlessField": "vless_enabled",
"ldapSyncCron": "@every 1m", "ldapSyncCron": "@every 1m",
"ldapFlagField": "", "ldapFlagField": "",
"ldapTruthyValues": "true,1,yes,on", "ldapTruthyValues": "true,1,yes,on",
"ldapInvertFlag": "false", "ldapInvertFlag": "false",
"ldapInboundTags": "", "ldapInboundTags": "",
"ldapAutoCreate": "false", "ldapAutoCreate": "false",
"ldapAutoDelete": "false", "ldapAutoDelete": "false",
"ldapDefaultTotalGB": "0", "ldapDefaultTotalGB": "0",
"ldapDefaultExpiryDays": "0", "ldapDefaultExpiryDays": "0",
"ldapDefaultLimitIP": "0", "ldapDefaultLimitIP": "0",
} }
// SettingService provides business logic for application settings management. // SettingService provides business logic for application settings management.
@ -565,83 +565,83 @@ func (s *SettingService) GetIpLimitEnable() (bool, error) {
// LDAP exported getters // LDAP exported getters
func (s *SettingService) GetLdapEnable() (bool, error) { func (s *SettingService) GetLdapEnable() (bool, error) {
return s.getBool("ldapEnable") return s.getBool("ldapEnable")
} }
func (s *SettingService) GetLdapHost() (string, error) { func (s *SettingService) GetLdapHost() (string, error) {
return s.getString("ldapHost") return s.getString("ldapHost")
} }
func (s *SettingService) GetLdapPort() (int, error) { func (s *SettingService) GetLdapPort() (int, error) {
return s.getInt("ldapPort") return s.getInt("ldapPort")
} }
func (s *SettingService) GetLdapUseTLS() (bool, error) { func (s *SettingService) GetLdapUseTLS() (bool, error) {
return s.getBool("ldapUseTLS") return s.getBool("ldapUseTLS")
} }
func (s *SettingService) GetLdapBindDN() (string, error) { func (s *SettingService) GetLdapBindDN() (string, error) {
return s.getString("ldapBindDN") return s.getString("ldapBindDN")
} }
func (s *SettingService) GetLdapPassword() (string, error) { func (s *SettingService) GetLdapPassword() (string, error) {
return s.getString("ldapPassword") return s.getString("ldapPassword")
} }
func (s *SettingService) GetLdapBaseDN() (string, error) { func (s *SettingService) GetLdapBaseDN() (string, error) {
return s.getString("ldapBaseDN") return s.getString("ldapBaseDN")
} }
func (s *SettingService) GetLdapUserFilter() (string, error) { func (s *SettingService) GetLdapUserFilter() (string, error) {
return s.getString("ldapUserFilter") return s.getString("ldapUserFilter")
} }
func (s *SettingService) GetLdapUserAttr() (string, error) { func (s *SettingService) GetLdapUserAttr() (string, error) {
return s.getString("ldapUserAttr") return s.getString("ldapUserAttr")
} }
func (s *SettingService) GetLdapVlessField() (string, error) { func (s *SettingService) GetLdapVlessField() (string, error) {
return s.getString("ldapVlessField") return s.getString("ldapVlessField")
} }
func (s *SettingService) GetLdapSyncCron() (string, error) { func (s *SettingService) GetLdapSyncCron() (string, error) {
return s.getString("ldapSyncCron") return s.getString("ldapSyncCron")
} }
func (s *SettingService) GetLdapFlagField() (string, error) { func (s *SettingService) GetLdapFlagField() (string, error) {
return s.getString("ldapFlagField") return s.getString("ldapFlagField")
} }
func (s *SettingService) GetLdapTruthyValues() (string, error) { func (s *SettingService) GetLdapTruthyValues() (string, error) {
return s.getString("ldapTruthyValues") return s.getString("ldapTruthyValues")
} }
func (s *SettingService) GetLdapInvertFlag() (bool, error) { func (s *SettingService) GetLdapInvertFlag() (bool, error) {
return s.getBool("ldapInvertFlag") return s.getBool("ldapInvertFlag")
} }
func (s *SettingService) GetLdapInboundTags() (string, error) { func (s *SettingService) GetLdapInboundTags() (string, error) {
return s.getString("ldapInboundTags") return s.getString("ldapInboundTags")
} }
func (s *SettingService) GetLdapAutoCreate() (bool, error) { func (s *SettingService) GetLdapAutoCreate() (bool, error) {
return s.getBool("ldapAutoCreate") return s.getBool("ldapAutoCreate")
} }
func (s *SettingService) GetLdapAutoDelete() (bool, error) { func (s *SettingService) GetLdapAutoDelete() (bool, error) {
return s.getBool("ldapAutoDelete") return s.getBool("ldapAutoDelete")
} }
func (s *SettingService) GetLdapDefaultTotalGB() (int, error) { func (s *SettingService) GetLdapDefaultTotalGB() (int, error) {
return s.getInt("ldapDefaultTotalGB") return s.getInt("ldapDefaultTotalGB")
} }
func (s *SettingService) GetLdapDefaultExpiryDays() (int, error) { func (s *SettingService) GetLdapDefaultExpiryDays() (int, error) {
return s.getInt("ldapDefaultExpiryDays") return s.getInt("ldapDefaultExpiryDays")
} }
func (s *SettingService) GetLdapDefaultLimitIP() (int, error) { func (s *SettingService) GetLdapDefaultLimitIP() (int, error) {
return s.getInt("ldapDefaultLimitIP") return s.getInt("ldapDefaultLimitIP")
} }
func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting) error { func (s *SettingService) UpdateAllSetting(allSetting *entity.AllSetting) error {

View file

@ -7,7 +7,7 @@ import (
"github.com/mhsanaei/3x-ui/v2/database/model" "github.com/mhsanaei/3x-ui/v2/database/model"
"github.com/mhsanaei/3x-ui/v2/logger" "github.com/mhsanaei/3x-ui/v2/logger"
"github.com/mhsanaei/3x-ui/v2/util/crypto" "github.com/mhsanaei/3x-ui/v2/util/crypto"
ldaputil "github.com/mhsanaei/3x-ui/v2/util/ldap" ldaputil "github.com/mhsanaei/3x-ui/v2/util/ldap"
"github.com/xlzd/gotp" "github.com/xlzd/gotp"
"gorm.io/gorm" "gorm.io/gorm"
) )
@ -49,38 +49,38 @@ func (s *UserService) CheckUser(username string, password string, twoFactorCode
return nil return nil
} }
// If LDAP enabled and local password check fails, attempt LDAP auth // If LDAP enabled and local password check fails, attempt LDAP auth
if !crypto.CheckPasswordHash(user.Password, password) { if !crypto.CheckPasswordHash(user.Password, password) {
ldapEnabled, _ := s.settingService.GetLdapEnable() ldapEnabled, _ := s.settingService.GetLdapEnable()
if !ldapEnabled { if !ldapEnabled {
return nil return nil
} }
host, _ := s.settingService.GetLdapHost() host, _ := s.settingService.GetLdapHost()
port, _ := s.settingService.GetLdapPort() port, _ := s.settingService.GetLdapPort()
useTLS, _ := s.settingService.GetLdapUseTLS() useTLS, _ := s.settingService.GetLdapUseTLS()
bindDN, _ := s.settingService.GetLdapBindDN() bindDN, _ := s.settingService.GetLdapBindDN()
ldapPass, _ := s.settingService.GetLdapPassword() ldapPass, _ := s.settingService.GetLdapPassword()
baseDN, _ := s.settingService.GetLdapBaseDN() baseDN, _ := s.settingService.GetLdapBaseDN()
userFilter, _ := s.settingService.GetLdapUserFilter() userFilter, _ := s.settingService.GetLdapUserFilter()
userAttr, _ := s.settingService.GetLdapUserAttr() userAttr, _ := s.settingService.GetLdapUserAttr()
cfg := ldaputil.Config{ cfg := ldaputil.Config{
Host: host, Host: host,
Port: port, Port: port,
UseTLS: useTLS, UseTLS: useTLS,
BindDN: bindDN, BindDN: bindDN,
Password: ldapPass, Password: ldapPass,
BaseDN: baseDN, BaseDN: baseDN,
UserFilter: userFilter, UserFilter: userFilter,
UserAttr: userAttr, UserAttr: userAttr,
} }
ok, err := ldaputil.AuthenticateUser(cfg, username, password) ok, err := ldaputil.AuthenticateUser(cfg, username, password)
if err != nil || !ok { if err != nil || !ok {
return nil return nil
} }
// On successful LDAP auth, continue 2FA checks below // On successful LDAP auth, continue 2FA checks below
} }
twoFactorEnable, err := s.settingService.GetTwoFactorEnable() twoFactorEnable, err := s.settingService.GetTwoFactorEnable()
if err != nil { if err != nil {