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