131 lines
3.6 KiB
Go
Raw Normal View History

2018-01-26 12:38:19 +01:00
package app
import (
"crypto/tls"
"fmt"
"log"
"strings"
"gopkg.in/ldap.v2"
)
// SecurityProtocol protocol type
type SecurityProtocol int
// Note: new type must be added at the end of list to maintain compatibility.
const (
SecurityProtocolUnencrypted SecurityProtocol = iota
SecurityProtocolLDAPS
SecurityProtocolStartTLS
)
// LDAPClient Basic LDAP authentication service
type LDAPClient struct {
Name string // canonical name (ie. corporate.ad)
Host string // LDAP host
Port int // port number
SecurityProtocol SecurityProtocol
SkipVerify bool
UserBase string // Base search path for users
UserDN string // Template for the DN of the user for simple auth
Enabled bool // if this LDAPClient is disabled
}
func bindUser(l *ldap.Conn, userDN, passwd string) error {
log.Printf("\nBinding with userDN: %s", userDN)
err := l.Bind(userDN, passwd)
if err != nil {
log.Printf("\nLDAP auth. failed for %s, reason: %v", userDN, err)
return err
}
log.Printf("\nBound successfully with userDN: %s", userDN)
return err
}
func (ls *LDAPClient) sanitizedUserDN(username string) (string, bool) {
// See http://tools.ietf.org/search/rfc4514: "special characters"
badCharacters := "\x00()*\\,='\"#+;<>"
if strings.ContainsAny(username, badCharacters) {
log.Printf("\n'%s' contains invalid DN characters. Aborting.", username)
return "", false
}
return fmt.Sprintf(ls.UserDN, username), true
}
func dial(ls *LDAPClient) (*ldap.Conn, error) {
log.Printf("\nDialing LDAP with security protocol (%v) without verifying: %v", ls.SecurityProtocol, ls.SkipVerify)
tlsCfg := &tls.Config{
ServerName: ls.Host,
InsecureSkipVerify: ls.SkipVerify,
}
if ls.SecurityProtocol == SecurityProtocolLDAPS {
return ldap.DialTLS("tcp", fmt.Sprintf("%s:%d", ls.Host, ls.Port), tlsCfg)
}
conn, err := ldap.Dial("tcp", fmt.Sprintf("%s:%d", ls.Host, ls.Port))
if err != nil {
return nil, fmt.Errorf("Dial: %v", err)
}
if ls.SecurityProtocol == SecurityProtocolStartTLS {
if err = conn.StartTLS(tlsCfg); err != nil {
conn.Close()
return nil, fmt.Errorf("StartTLS: %v", err)
}
}
return conn, nil
}
// ModifyPassword : modify user's password
func (ls *LDAPClient) ModifyPassword(name, passwd, newPassword string) error {
if len(passwd) == 0 {
return fmt.Errorf("Auth. failed for %s, password cannot be empty", name)
}
l, err := dial(ls)
if err != nil {
ls.Enabled = false
return fmt.Errorf("LDAP Connect error, %s:%v", ls.Host, err)
}
defer l.Close()
var userDN string
log.Printf("\nLDAP will bind directly via UserDN template: %s", ls.UserDN)
var ok bool
userDN, ok = ls.sanitizedUserDN(name)
if !ok {
return fmt.Errorf("Error sanitizing name %s", name)
}
bindUser(l, userDN, passwd)
log.Printf("\nLDAP will execute password change on: %s", userDN)
req := ldap.NewPasswordModifyRequest(userDN, passwd, newPassword)
_, err = l.PasswordModify(req)
return err
}
// NewLDAPClient : Creates new LDAPClient capable of binding and changing passwords
func NewLDAPClient() *LDAPClient {
securityProtocol := SecurityProtocolUnencrypted
if envBool("LPW_ENCRYPTED", true) {
securityProtocol = SecurityProtocolLDAPS
if envBool("LPW_START_TLS", false) {
securityProtocol = SecurityProtocolStartTLS
}
}
return &LDAPClient{
Host: envStr("LPW_HOST", ""),
Port: envInt("LPW_PORT", 636), // 389
SecurityProtocol: securityProtocol,
SkipVerify: envBool("LPW_SSL_SKIP_VERIFY", false),
UserDN: envStr("LPW_USER_DN", "uid=%s,ou=people,dc=example,dc=org"),
UserBase: envStr("LPW_USER_BASE", "ou=people,dc=example,dc=org"),
}
}