File size: 4,142 Bytes
7107f0b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
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)

	// Connect to LdapServer
	l, err := dial(ldapServer)
	if err != nil {
		utils.Log.Errorf("failed to connect to LDAP: %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
}

func dial(ldapServer string) (*ldap.Conn, error) {
	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://")
	}

	if tlsEnabled {
		return ldap.DialTLS("tcp", ldapServer, &tls.Config{InsecureSkipVerify: true})
	} else {
		return ldap.Dial("tcp", ldapServer)
	}
}