grafana/pkg/services/multildap/multildap.go

253 lines
5.6 KiB
Go
Raw Normal View History

package multildap
import (
"errors"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/ldap"
)
// logger to log
var logger = log.New("ldap")
// GetConfig gets LDAP config
var GetConfig = ldap.GetConfig
// IsEnabled checks if LDAP is enabled
var IsEnabled = ldap.IsEnabled
// newLDAP return instance of the single LDAP server
var newLDAP = ldap.New
// ErrInvalidCredentials is returned if username and password do not match
var ErrInvalidCredentials = ldap.ErrInvalidCredentials
// ErrCouldNotFindUser is returned when username hasn't been found (not username+password)
var ErrCouldNotFindUser = ldap.ErrCouldNotFindUser
// ErrNoLDAPServers is returned when there is no LDAP servers specified
var ErrNoLDAPServers = errors.New("no LDAP servers are configured")
// ErrDidNotFindUser if request for user is unsuccessful
var ErrDidNotFindUser = errors.New("did not find a user")
// ServerStatus holds the LDAP server status
type ServerStatus struct {
Host string
Port int
Available bool
Error error
}
// IMultiLDAP is interface for MultiLDAP
type IMultiLDAP interface {
Ping() ([]*ServerStatus, error)
Login(query *models.LoginUserQuery) (
*models.ExternalUserInfo, error,
)
Users(logins []string) (
[]*models.ExternalUserInfo, error,
)
User(login string) (
*models.ExternalUserInfo, ldap.ServerConfig, error,
)
}
// MultiLDAP is basic struct of LDAP authorization
type MultiLDAP struct {
configs []*ldap.ServerConfig
}
// New creates the new LDAP auth
func New(configs []*ldap.ServerConfig) IMultiLDAP {
return &MultiLDAP{
configs: configs,
}
}
// Ping dials each of the LDAP servers and returns their status. If the server is unavailable, it also returns the error.
func (multiples *MultiLDAP) Ping() ([]*ServerStatus, error) {
if len(multiples.configs) == 0 {
return nil, ErrNoLDAPServers
}
serverStatuses := []*ServerStatus{}
for _, config := range multiples.configs {
status := &ServerStatus{}
status.Host = config.Host
status.Port = config.Port
server := newLDAP(config)
err := server.Dial()
if err == nil {
status.Available = true
serverStatuses = append(serverStatuses, status)
server.Close()
} else {
status.Available = false
status.Error = err
serverStatuses = append(serverStatuses, status)
}
}
return serverStatuses, nil
}
// Login tries to log in the user in multiples LDAP
func (multiples *MultiLDAP) Login(query *models.LoginUserQuery) (
*models.ExternalUserInfo, error,
) {
if len(multiples.configs) == 0 {
return nil, ErrNoLDAPServers
}
for index, config := range multiples.configs {
server := newLDAP(config)
if err := server.Dial(); err != nil {
logDialFailure(err, config)
// Only return an error if it is the last server so we can try next server
if index == len(multiples.configs)-1 {
return nil, err
}
continue
}
defer server.Close()
user, err := server.Login(query)
// FIXME
if user != nil {
return user, nil
}
if err != nil {
if isSilentError(err) {
logger.Debug(
"unable to login with LDAP - skipping server",
"host", config.Host,
"port", config.Port,
"error", err,
)
continue
}
return nil, err
}
}
// Return invalid credentials if we couldn't find the user anywhere
return nil, ErrInvalidCredentials
}
// User attempts to find an user by login/username by searching into all of the configured LDAP servers. Then, if the user is found it returns the user alongisde the server it was found.
func (multiples *MultiLDAP) User(login string) (
*models.ExternalUserInfo,
ldap.ServerConfig,
error,
) {
if len(multiples.configs) == 0 {
return nil, ldap.ServerConfig{}, ErrNoLDAPServers
}
search := []string{login}
for index, config := range multiples.configs {
server := newLDAP(config)
if err := server.Dial(); err != nil {
logDialFailure(err, config)
// Only return an error if it is the last server so we can try next server
if index == len(multiples.configs)-1 {
return nil, *config, err
}
continue
}
defer server.Close()
if err := server.Bind(); err != nil {
return nil, *config, err
}
users, err := server.Users(search)
if err != nil {
return nil, *config, err
}
if len(users) != 0 {
return users[0], *config, nil
}
}
return nil, ldap.ServerConfig{}, ErrDidNotFindUser
}
// Users gets users from multiple LDAP servers
func (multiples *MultiLDAP) Users(logins []string) (
[]*models.ExternalUserInfo,
error,
) {
var result []*models.ExternalUserInfo
if len(multiples.configs) == 0 {
return nil, ErrNoLDAPServers
}
for index, config := range multiples.configs {
server := newLDAP(config)
if err := server.Dial(); err != nil {
logDialFailure(err, config)
// Only return an error if it is the last server so we can try next server
if index == len(multiples.configs)-1 {
return nil, err
}
continue
}
defer server.Close()
if err := server.Bind(); err != nil {
return nil, err
}
users, err := server.Users(logins)
if err != nil {
return nil, err
}
result = append(result, users...)
}
return result, nil
}
// isSilentError evaluates an error and tells whenever we should fail the LDAP request
// immediately or if we should continue into other LDAP servers
func isSilentError(err error) bool {
continueErrs := []error{ErrInvalidCredentials, ErrCouldNotFindUser}
for _, cerr := range continueErrs {
if errors.Is(err, cerr) {
return true
}
}
return false
}
func logDialFailure(err error, config *ldap.ServerConfig) {
logger.Debug(
"unable to dial LDAP server",
"host", config.Host,
"port", config.Port,
"error", err,
)
}