2019-05-17 14:57:26 +03:00
package multildap
import (
"errors"
2019-09-23 13:34:05 +01:00
"github.com/grafana/grafana/pkg/infra/log"
2019-05-17 14:57:26 +03:00
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/ldap"
)
2019-09-23 13:34:05 +01:00
// logger to log
var logger = log . New ( "ldap" )
2019-05-17 14:57:26 +03:00
// GetConfig gets LDAP config
var GetConfig = ldap . GetConfig
// IsEnabled checks if LDAP is enabled
var IsEnabled = ldap . IsEnabled
2019-06-19 15:46:04 +02:00
// newLDAP return instance of the single LDAP server
var newLDAP = ldap . New
2019-05-17 14:57:26 +03:00
// ErrInvalidCredentials is returned if username and password do not match
var ErrInvalidCredentials = ldap . ErrInvalidCredentials
2019-08-19 00:52:32 +03:00
// ErrCouldNotFindUser is returned when username hasn't been found (not username+password)
var ErrCouldNotFindUser = ldap . ErrCouldNotFindUser
2019-05-17 14:57:26 +03:00
// ErrNoLDAPServers is returned when there is no LDAP servers specified
2020-11-05 11:57:20 +01:00
var ErrNoLDAPServers = errors . New ( "no LDAP servers are configured" )
2019-05-17 14:57:26 +03:00
// ErrDidNotFindUser if request for user is unsuccessful
2020-11-05 11:57:20 +01:00
var ErrDidNotFindUser = errors . New ( "did not find a user" )
2019-05-17 14:57:26 +03:00
2019-09-04 15:29:14 +01:00
// ServerStatus holds the LDAP server status
type ServerStatus struct {
Host string
Port int
Available bool
Error error
}
2019-05-17 14:57:26 +03:00
// IMultiLDAP is interface for MultiLDAP
type IMultiLDAP interface {
2019-09-04 15:29:14 +01:00
Ping () ([] * ServerStatus , error )
2019-05-17 14:57:26 +03:00
Login ( query * models . LoginUserQuery ) (
* models . ExternalUserInfo , error ,
)
Users ( logins [] string ) (
[] * models . ExternalUserInfo , error ,
)
User ( login string ) (
2019-09-03 18:34:44 +01:00
* models . ExternalUserInfo , ldap . ServerConfig , error ,
2019-05-17 14:57:26 +03:00
)
}
// 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 ,
}
}
2019-09-04 15:29:14 +01:00
// 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 )
2019-09-16 14:13:35 +01:00
server . Close ()
2019-09-04 15:29:14 +01:00
} else {
status . Available = false
status . Error = err
serverStatuses = append ( serverStatuses , status )
}
}
return serverStatuses , nil
}
2019-05-17 14:57:26 +03:00
// 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
}
2019-11-05 08:58:59 +01:00
for index , config := range multiples . configs {
2019-06-07 15:16:00 +03:00
server := newLDAP ( config )
2019-05-17 14:57:26 +03:00
if err := server . Dial (); err != nil {
2019-11-05 08:58:59 +01:00
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
2019-05-17 14:57:26 +03:00
}
defer server . Close ()
user , err := server . Login ( query )
2020-07-06 20:17:28 +02:00
// FIXME
2019-05-17 14:57:26 +03:00
if user != nil {
return user , nil
}
if err != nil {
2019-09-23 13:34:05 +01:00
if isSilentError ( err ) {
logger . Debug (
"unable to login with LDAP - skipping server" ,
"host" , config . Host ,
"port" , config . Port ,
"error" , err ,
)
continue
}
2019-05-17 14:57:26 +03:00
return nil , err
}
}
// Return invalid credentials if we couldn't find the user anywhere
return nil , ErrInvalidCredentials
}
2019-09-03 18:34:44 +01:00
// 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.
2019-05-17 14:57:26 +03:00
func ( multiples * MultiLDAP ) User ( login string ) (
* models . ExternalUserInfo ,
2019-09-03 18:34:44 +01:00
ldap . ServerConfig ,
2019-05-17 14:57:26 +03:00
error ,
) {
if len ( multiples . configs ) == 0 {
2019-09-03 18:34:44 +01:00
return nil , ldap . ServerConfig {}, ErrNoLDAPServers
2019-05-17 14:57:26 +03:00
}
search := [] string { login }
2019-11-05 08:58:59 +01:00
for index , config := range multiples . configs {
2019-06-07 15:16:00 +03:00
server := newLDAP ( config )
2019-05-17 14:57:26 +03:00
if err := server . Dial (); err != nil {
2019-11-05 08:58:59 +01:00
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
2019-05-17 14:57:26 +03:00
}
defer server . Close ()
2019-07-10 12:25:21 +02:00
if err := server . Bind (); err != nil {
2019-09-03 18:34:44 +01:00
return nil , * config , err
2019-07-10 12:25:21 +02:00
}
2019-05-17 14:57:26 +03:00
users , err := server . Users ( search )
if err != nil {
2019-09-03 18:34:44 +01:00
return nil , * config , err
2019-05-17 14:57:26 +03:00
}
if len ( users ) != 0 {
2019-09-03 18:34:44 +01:00
return users [ 0 ], * config , nil
2019-05-17 14:57:26 +03:00
}
}
2019-09-03 18:34:44 +01:00
return nil , ldap . ServerConfig {}, ErrDidNotFindUser
2019-05-17 14:57:26 +03:00
}
// 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
}
2019-11-05 08:58:59 +01:00
for index , config := range multiples . configs {
2019-06-07 15:16:00 +03:00
server := newLDAP ( config )
2019-05-17 14:57:26 +03:00
if err := server . Dial (); err != nil {
2019-11-05 08:58:59 +01:00
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
2019-05-17 14:57:26 +03:00
}
defer server . Close ()
2019-07-10 12:25:21 +02:00
if err := server . Bind (); err != nil {
return nil , err
}
2019-05-17 14:57:26 +03:00
users , err := server . Users ( logins )
if err != nil {
return nil , err
}
result = append ( result , users ... )
}
return result , nil
}
2019-09-23 13:34:05 +01:00
// 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 {
2020-11-19 14:47:17 +01:00
if errors . Is ( err , cerr ) {
2019-09-23 13:34:05 +01:00
return true
}
}
return false
}
2019-11-05 08:58:59 +01:00
func logDialFailure ( err error , config * ldap . ServerConfig ) {
logger . Debug (
"unable to dial LDAP server" ,
"host" , config . Host ,
"port" , config . Port ,
"error" , err ,
)
}