-
Notifications
You must be signed in to change notification settings - Fork 2
/
ldap.go
168 lines (152 loc) · 4.64 KB
/
ldap.go
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
159
160
161
162
163
164
165
166
167
168
package authaus
import (
//"github.com/mmitton/ldap"
"crypto/tls"
"errors"
"fmt"
"strings"
"time"
"github.com/mavricknz/ldap"
)
type LdapConnectionMode int
const (
LdapConnectionModePlainText LdapConnectionMode = iota
LdapConnectionModeSSL = iota
LdapConnectionModeTLS = iota
)
type LdapImpl struct {
config *ConfigLDAP
}
func (x *LdapImpl) Authenticate(identity, password string) error {
if len(password) == 0 {
// Many LDAP servers (or AD) will allow an anonymous BIND.
// I've never seen the need for a password-less user authenticated against LDAP.
return ErrInvalidPassword
}
con, err := NewLDAPConnect(x.config)
if err != nil {
return err
}
defer con.Close()
// We need to know whether or not we must add the domain to the identity by checking if it contains '@'
if !strings.Contains(identity, "@") {
identity = fmt.Sprintf(`%v@%v`, identity, x.config.LdapDomain)
}
err = con.Bind(identity, password)
if err != nil {
if strings.Index(err.Error(), "Invalid Credentials") != 0 {
return ErrInvalidCredentials
} else {
return err
}
}
return nil
}
func (x *LdapImpl) Close() {
}
func (x *LdapImpl) GetLdapUsers() ([]AuthUser, error) {
var attributes []string = []string{
"sAMAccountName",
"givenName",
"name",
"sn",
"mail",
"mobile",
"userPrincipalName",
}
searchRequest := ldap.NewSearchRequest(
x.config.BaseDN,
ldap.ScopeWholeSubtree, ldap.DerefAlways, 0, 0, false,
x.config.LdapSearchFilter,
attributes,
nil)
con, err := NewLDAPConnectAndBind(x.config)
if err != nil {
return nil, err
}
defer con.Close()
sr, err := con.SearchWithPaging(searchRequest, 100)
if err != nil {
return nil, err
}
getAttributeValue := func(entry ldap.Entry, attribute string) string {
values := entry.GetAttributeValues(attribute)
if len(values) == 0 {
return ""
}
return values[0]
}
if x.config.DebugUserPull {
fmt.Printf("%23v | %16v | %19v | %45v | %15v\n", "username", "name", "surname", "email", "mobile")
}
ldapUsers := make([]AuthUser, len(sr.Entries))
for i, value := range sr.Entries {
// We trim the spaces as we have found that a certain ldap user
// (WilburGS) has an email that ends with a space.
username := strings.TrimSpace(getAttributeValue(*value, "sAMAccountName"))
givenName := strings.TrimSpace(getAttributeValue(*value, "givenName"))
name := strings.TrimSpace(getAttributeValue(*value, "name"))
surname := strings.TrimSpace(getAttributeValue(*value, "sn"))
email := strings.TrimSpace(getAttributeValue(*value, "mail"))
mobile := strings.TrimSpace(getAttributeValue(*value, "mobile"))
userPrincipalName := strings.TrimSpace(getAttributeValue(*value, "userPrincipalName"))
if email == "" && strings.Count(userPrincipalName, "@") == 1 {
// This was first seen in Azure, when integrating with DTPW (Department of Transport and Public Works)
email = userPrincipalName
}
firstName := givenName
if firstName == "" && surname == "" && name != "" {
// We're in dubious best-guess-for-common-english territory here
firstSpace := strings.Index(name, " ")
if firstSpace != -1 {
firstName = name[:firstSpace]
surname = name[firstSpace+1:]
}
}
if x.config.DebugUserPull {
fmt.Printf("%23v | %16v | %19v | %45v | %15v\n", username, firstName, surname, email, mobile)
}
ldapUsers[i] = AuthUser{UserId: NullUserId, Email: email, Username: username, Firstname: firstName, Lastname: surname, Mobilenumber: mobile}
}
return ldapUsers, nil
}
func NewLDAPConnectAndBind(config *ConfigLDAP) (*ldap.LDAPConnection, error) {
con, err := NewLDAPConnect(config)
if err != nil {
return nil, err
}
if err := con.Bind(config.LdapUsername, config.LdapPassword); err != nil {
return nil, err
}
return con, nil
}
func NewLDAPConnect(config *ConfigLDAP) (*ldap.LDAPConnection, error) {
con := ldap.NewLDAPConnection(config.LdapHost, config.LdapPort)
con.NetworkConnectTimeout = 30 * time.Second
con.ReadTimeout = 30 * time.Second
ldapMode, legalLdapMode := configLdapNameToMode[config.Encryption]
if !legalLdapMode {
return nil, errors.New(fmt.Sprintf("Unknown ldap mode %v. Recognized modes are TLS, SSL, and empty for unencrypted", config.Encryption))
}
switch ldapMode {
case LdapConnectionModePlainText:
case LdapConnectionModeSSL:
con.IsSSL = true
case LdapConnectionModeTLS:
con.IsTLS = true
}
if config.InsecureSkipVerify {
con.TlsConfig = &tls.Config{}
con.TlsConfig.InsecureSkipVerify = config.InsecureSkipVerify
}
if err := con.Connect(); err != nil {
con.Close()
return nil, err
}
return con, nil
}
func NewAuthenticator_LDAP(config *ConfigLDAP) *LdapImpl {
return &LdapImpl{
config: config,
}
}