mirror of
https://github.com/ergochat/ergo.git
synced 2024-12-23 19:22:41 +01:00
268 lines
6.5 KiB
Go
268 lines
6.5 KiB
Go
// Copyright 2014-2018 Grafana Labs
|
|
// Released under the Apache 2.0 license
|
|
|
|
// Modification notice:
|
|
// 1. `serverConn` was substituted for `Server` as the type of the server object
|
|
// 2. Debug loglines were altered to work with Oragono's logging system
|
|
|
|
package ldap
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"strings"
|
|
|
|
ldap "github.com/go-ldap/ldap/v3"
|
|
)
|
|
|
|
var (
|
|
// ErrInvalidCredentials is returned if username and password do not match
|
|
ErrInvalidCredentials = errors.New("Invalid Username or Password")
|
|
|
|
// ErrCouldNotFindUser is returned when username hasn't been found (not username+password)
|
|
ErrCouldNotFindUser = errors.New("Can't find user in LDAP")
|
|
)
|
|
|
|
// shouldAdminBind checks if we should use
|
|
// admin username & password for LDAP bind
|
|
func (server *serverConn) shouldAdminBind() bool {
|
|
return server.Config.BindPassword != ""
|
|
}
|
|
|
|
// singleBindDN combines the bind with the username
|
|
// in order to get the proper path
|
|
func (server *serverConn) singleBindDN(username string) string {
|
|
return fmt.Sprintf(server.Config.BindDN, username)
|
|
}
|
|
|
|
// shouldSingleBind checks if we can use "single bind" approach
|
|
func (server *serverConn) shouldSingleBind() bool {
|
|
return strings.Contains(server.Config.BindDN, "%s")
|
|
}
|
|
|
|
// Dial dials in the LDAP
|
|
// TODO: decrease cyclomatic complexity
|
|
func (server *serverConn) Dial() error {
|
|
var err error
|
|
var certPool *x509.CertPool
|
|
if server.Config.RootCACert != "" {
|
|
certPool = x509.NewCertPool()
|
|
for _, caCertFile := range strings.Split(server.Config.RootCACert, " ") {
|
|
pem, err := ioutil.ReadFile(caCertFile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !certPool.AppendCertsFromPEM(pem) {
|
|
return errors.New("Failed to append CA certificate " + caCertFile)
|
|
}
|
|
}
|
|
}
|
|
var clientCert tls.Certificate
|
|
if server.Config.ClientCert != "" && server.Config.ClientKey != "" {
|
|
clientCert, err = tls.LoadX509KeyPair(server.Config.ClientCert, server.Config.ClientKey)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
for _, host := range strings.Split(server.Config.Host, " ") {
|
|
address := fmt.Sprintf("%s:%d", host, server.Config.Port)
|
|
if server.Config.UseSSL {
|
|
tlsCfg := &tls.Config{
|
|
InsecureSkipVerify: server.Config.SkipVerifySSL,
|
|
ServerName: host,
|
|
RootCAs: certPool,
|
|
}
|
|
if len(clientCert.Certificate) > 0 {
|
|
tlsCfg.Certificates = append(tlsCfg.Certificates, clientCert)
|
|
}
|
|
if server.Config.StartTLS {
|
|
server.Connection, err = ldap.Dial("tcp", address)
|
|
if err == nil {
|
|
if err = server.Connection.StartTLS(tlsCfg); err == nil {
|
|
return nil
|
|
}
|
|
}
|
|
} else {
|
|
server.Connection, err = ldap.DialTLS("tcp", address, tlsCfg)
|
|
}
|
|
} else {
|
|
server.Connection, err = ldap.Dial("tcp", address)
|
|
}
|
|
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
// Close closes the LDAP connection
|
|
// Dial() sets the connection with the server for this Struct. Therefore, we require a
|
|
// call to Dial() before being able to execute this function.
|
|
func (server *serverConn) Close() {
|
|
server.Connection.Close()
|
|
}
|
|
|
|
// userBind binds the user with the LDAP server
|
|
func (server *serverConn) userBind(path, password string) error {
|
|
err := server.Connection.Bind(path, password)
|
|
if err != nil {
|
|
if ldapErr, ok := err.(*ldap.Error); ok {
|
|
if ldapErr.ResultCode == 49 {
|
|
return ErrInvalidCredentials
|
|
}
|
|
}
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// users is helper method for the Users()
|
|
func (server *serverConn) users(logins []string) (
|
|
[]*ldap.Entry,
|
|
error,
|
|
) {
|
|
var result *ldap.SearchResult
|
|
var Config = server.Config
|
|
var err error
|
|
|
|
for _, base := range Config.SearchBaseDNs {
|
|
result, err = server.Connection.Search(
|
|
server.getSearchRequest(base, logins),
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(result.Entries) > 0 {
|
|
break
|
|
}
|
|
}
|
|
|
|
return result.Entries, nil
|
|
}
|
|
|
|
// getSearchRequest returns LDAP search request for users
|
|
func (server *serverConn) getSearchRequest(
|
|
base string,
|
|
logins []string,
|
|
) *ldap.SearchRequest {
|
|
attributes := []string{}
|
|
|
|
inputs := server.Config.Attr
|
|
attributes = appendIfNotEmpty(
|
|
attributes,
|
|
inputs.Username,
|
|
inputs.Surname,
|
|
inputs.Email,
|
|
inputs.Name,
|
|
inputs.MemberOf,
|
|
|
|
// In case for the POSIX LDAP schema server
|
|
server.Config.GroupSearchFilterUserAttribute,
|
|
)
|
|
|
|
search := ""
|
|
for _, login := range logins {
|
|
query := strings.Replace(
|
|
server.Config.SearchFilter,
|
|
"%s", ldap.EscapeFilter(login),
|
|
-1,
|
|
)
|
|
|
|
search = search + query
|
|
}
|
|
|
|
filter := fmt.Sprintf("(|%s)", search)
|
|
|
|
return &ldap.SearchRequest{
|
|
BaseDN: base,
|
|
Scope: ldap.ScopeWholeSubtree,
|
|
DerefAliases: ldap.NeverDerefAliases,
|
|
Attributes: attributes,
|
|
Filter: filter,
|
|
}
|
|
}
|
|
|
|
// requestMemberOf use this function when POSIX LDAP
|
|
// schema does not support memberOf, so it manually search the groups
|
|
func (server *serverConn) requestMemberOf(entry *ldap.Entry) ([]string, error) {
|
|
var memberOf []string
|
|
var config = server.Config
|
|
|
|
for _, groupSearchBase := range config.GroupSearchBaseDNs {
|
|
var filterReplace string
|
|
if config.GroupSearchFilterUserAttribute == "" {
|
|
filterReplace = getAttribute(config.Attr.Username, entry)
|
|
} else {
|
|
filterReplace = getAttribute(
|
|
config.GroupSearchFilterUserAttribute,
|
|
entry,
|
|
)
|
|
}
|
|
|
|
filter := strings.Replace(
|
|
config.GroupSearchFilter, "%s",
|
|
ldap.EscapeFilter(filterReplace),
|
|
-1,
|
|
)
|
|
|
|
server.logger.Debug("ldap", "Searching for groups with filter", filter)
|
|
|
|
// support old way of reading settings
|
|
groupIDAttribute := config.Attr.MemberOf
|
|
// but prefer dn attribute if default settings are used
|
|
if groupIDAttribute == "" || groupIDAttribute == "memberOf" {
|
|
groupIDAttribute = "dn"
|
|
}
|
|
|
|
groupSearchReq := ldap.SearchRequest{
|
|
BaseDN: groupSearchBase,
|
|
Scope: ldap.ScopeWholeSubtree,
|
|
DerefAliases: ldap.NeverDerefAliases,
|
|
Attributes: []string{groupIDAttribute},
|
|
Filter: filter,
|
|
}
|
|
|
|
groupSearchResult, err := server.Connection.Search(&groupSearchReq)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(groupSearchResult.Entries) > 0 {
|
|
for _, group := range groupSearchResult.Entries {
|
|
|
|
memberOf = append(
|
|
memberOf,
|
|
getAttribute(groupIDAttribute, group),
|
|
)
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
return memberOf, nil
|
|
}
|
|
|
|
// getMemberOf finds memberOf property or request it
|
|
func (server *serverConn) getMemberOf(result *ldap.Entry) (
|
|
[]string, error,
|
|
) {
|
|
if server.Config.GroupSearchFilter == "" {
|
|
memberOf := getArrayAttribute(server.Config.Attr.MemberOf, result)
|
|
|
|
return memberOf, nil
|
|
}
|
|
|
|
memberOf, err := server.requestMemberOf(result)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return memberOf, nil
|
|
}
|