2020-02-11 12:35:17 +01:00
|
|
|
// Copyright (c) 2020 Matt Ouille
|
|
|
|
// Copyright (c) 2020 Shivaram Lingamneni
|
|
|
|
// released under the MIT license
|
|
|
|
|
|
|
|
// Portions of this code copyright Grafana Labs and contributors
|
|
|
|
// and released under the Apache 2.0 license
|
|
|
|
|
|
|
|
// Copying Grafana's original comment on the different cases for LDAP:
|
|
|
|
// There are several cases -
|
|
|
|
// 1. "admin" user
|
|
|
|
// Bind the "admin" user (defined in Grafana config file) which has the search privileges
|
|
|
|
// in LDAP server, then we search the targeted user through that bind, then the second
|
|
|
|
// perform the bind via passed login/password.
|
|
|
|
// 2. Single bind
|
|
|
|
// // If all the users meant to be used with Grafana have the ability to search in LDAP server
|
|
|
|
// then we bind with LDAP server with targeted login/password
|
|
|
|
// and then search for the said user in order to retrive all the information about them
|
|
|
|
// 3. Unauthenticated bind
|
|
|
|
// For some LDAP configurations it is allowed to search the
|
|
|
|
// user without login/password binding with LDAP server, in such case
|
|
|
|
// we will perform "unauthenticated bind", then search for the
|
|
|
|
// targeted user and then perform the bind with passed login/password.
|
|
|
|
|
|
|
|
// Note: the only validation we do on users is to check RequiredGroups.
|
|
|
|
// If RequiredGroups is not set and we can do a single bind, we don't
|
|
|
|
// even need to search. So our case 2 is not restricted
|
|
|
|
// to setups where all the users have search privileges: we only need to
|
|
|
|
// be able to do DN resolution via pure string substitution.
|
|
|
|
|
|
|
|
package ldap
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
ldap "github.com/go-ldap/ldap/v3"
|
|
|
|
|
|
|
|
"github.com/oragono/oragono/irc/logger"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
ErrUserNotInRequiredGroup = errors.New("User is not a member of any required groups")
|
|
|
|
)
|
|
|
|
|
2020-02-11 22:09:43 +01:00
|
|
|
// equivalent of Grafana's `Server`, but unexported
|
2020-02-12 04:08:41 +01:00
|
|
|
// also, `log` was renamed to `logger`, since the APIs are slightly different
|
|
|
|
// and this way the compiler will catch any unchanged references to Grafana's `Server.log`
|
2020-02-11 22:09:43 +01:00
|
|
|
type serverConn struct {
|
|
|
|
Config *ServerConfig
|
|
|
|
Connection *ldap.Conn
|
2020-02-12 04:08:41 +01:00
|
|
|
logger *logger.Manager
|
2020-02-11 22:09:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func CheckLDAPPassphrase(config ServerConfig, accountName, passphrase string, log *logger.Manager) (err error) {
|
2020-02-11 12:35:17 +01:00
|
|
|
defer func() {
|
|
|
|
if err != nil {
|
|
|
|
log.Debug("ldap", "failed passphrase check", err.Error())
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2020-02-11 22:09:43 +01:00
|
|
|
server := serverConn{
|
|
|
|
Config: &config,
|
2020-02-12 04:08:41 +01:00
|
|
|
logger: log,
|
2020-02-11 22:09:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
err = server.Dial()
|
2020-02-11 12:35:17 +01:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
2020-02-11 22:09:43 +01:00
|
|
|
defer server.Close()
|
2020-02-11 12:35:17 +01:00
|
|
|
|
2020-02-11 22:09:43 +01:00
|
|
|
server.Connection.SetTimeout(config.Timeout)
|
2020-02-11 12:35:17 +01:00
|
|
|
|
|
|
|
passphraseChecked := false
|
|
|
|
|
2020-02-11 22:09:43 +01:00
|
|
|
if server.shouldSingleBind() {
|
2020-02-11 12:35:17 +01:00
|
|
|
log.Debug("ldap", "attempting single bind to", accountName)
|
2020-02-11 22:09:43 +01:00
|
|
|
err = server.userBind(server.singleBindDN(accountName), passphrase)
|
2020-02-11 12:35:17 +01:00
|
|
|
passphraseChecked = (err == nil)
|
2020-02-11 22:09:43 +01:00
|
|
|
} else if server.shouldAdminBind() {
|
2020-02-11 12:35:17 +01:00
|
|
|
log.Debug("ldap", "attempting admin bind to", config.BindDN)
|
2020-02-11 22:09:43 +01:00
|
|
|
err = server.userBind(config.BindDN, config.BindPassword)
|
2020-02-11 12:35:17 +01:00
|
|
|
} else {
|
|
|
|
log.Debug("ldap", "attempting unauthenticated bind")
|
2020-02-11 22:09:43 +01:00
|
|
|
err = server.Connection.UnauthenticatedBind(config.BindDN)
|
2020-02-11 12:35:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if passphraseChecked && len(config.RequireGroups) == 0 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-02-11 22:09:43 +01:00
|
|
|
users, err := server.users([]string{accountName})
|
2020-02-11 12:35:17 +01:00
|
|
|
if err != nil {
|
|
|
|
log.Debug("ldap", "failed user lookup")
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(users) == 0 {
|
|
|
|
return ErrCouldNotFindUser
|
|
|
|
}
|
|
|
|
|
|
|
|
user := users[0]
|
|
|
|
|
|
|
|
log.Debug("ldap", "looked up user", user.DN)
|
|
|
|
|
2020-02-11 22:09:43 +01:00
|
|
|
err = server.validateGroupMembership(user)
|
2020-02-11 12:35:17 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !passphraseChecked {
|
|
|
|
log.Debug("ldap", "rebinding", user.DN)
|
2020-02-11 22:09:43 +01:00
|
|
|
err = server.userBind(user.DN, passphrase)
|
2020-02-11 12:35:17 +01:00
|
|
|
}
|
|
|
|
|
2020-02-11 22:09:43 +01:00
|
|
|
return err
|
2020-02-11 12:35:17 +01:00
|
|
|
}
|
|
|
|
|
2020-02-11 22:09:43 +01:00
|
|
|
func (server *serverConn) validateGroupMembership(user *ldap.Entry) (err error) {
|
|
|
|
if len(server.Config.RequireGroups) == 0 {
|
|
|
|
return
|
2020-02-11 12:35:17 +01:00
|
|
|
}
|
|
|
|
|
2020-02-11 22:09:43 +01:00
|
|
|
var memberOf []string
|
|
|
|
memberOf, err = server.getMemberOf(user)
|
|
|
|
if err != nil {
|
2020-02-12 04:08:41 +01:00
|
|
|
server.logger.Debug("ldap", "could not retrieve group memberships", err.Error())
|
2020-02-11 22:09:43 +01:00
|
|
|
return
|
2020-02-11 12:35:17 +01:00
|
|
|
}
|
2020-02-12 04:08:41 +01:00
|
|
|
server.logger.Debug("ldap", fmt.Sprintf("found group memberships: %v", memberOf))
|
2020-02-11 22:09:43 +01:00
|
|
|
foundGroup := false
|
|
|
|
for _, inGroup := range memberOf {
|
|
|
|
for _, acceptableGroup := range server.Config.RequireGroups {
|
|
|
|
if inGroup == acceptableGroup {
|
|
|
|
foundGroup = true
|
2020-02-11 12:35:17 +01:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2020-02-11 22:09:43 +01:00
|
|
|
if foundGroup {
|
2020-02-11 12:35:17 +01:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2020-02-11 22:09:43 +01:00
|
|
|
if foundGroup {
|
|
|
|
return nil
|
|
|
|
} else {
|
|
|
|
return ErrUserNotInRequiredGroup
|
|
|
|
}
|
2020-02-11 12:35:17 +01:00
|
|
|
}
|