2019-05-17 14:57:26 +03:00
package multildap
import (
"errors"
2023-02-10 19:01:55 +01:00
"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
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/services/ldap"
2023-01-27 13:36:54 -05:00
"github.com/grafana/grafana/pkg/services/login"
2023-02-10 19:01:55 +01:00
"github.com/grafana/grafana/pkg/setting"
2019-05-17 14:57:26 +03:00
)
// GetConfig gets LDAP config
var GetConfig = ldap . GetConfig
2019-06-19 15:46:04 +02:00
// newLDAP return instance of the single LDAP server
var newLDAP = ldap . New
2023-02-10 19:01:55 +01:00
var (
// ErrInvalidCredentials is returned if username and password do not match
ErrInvalidCredentials = ldap . ErrInvalidCredentials
// ErrCouldNotFindUser is returned when username hasn't been found (not username+password)
ErrCouldNotFindUser = ldap . ErrCouldNotFindUser
// ErrNoLDAPServers is returned when there is no LDAP servers specified
ErrNoLDAPServers = errors . New ( "no LDAP servers are configured" )
// ErrDidNotFindUser if request for user is unsuccessful
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 )
2023-01-27 13:36:54 -05:00
Login ( query * login . LoginUserQuery ) (
* login . ExternalUserInfo , error ,
2019-05-17 14:57:26 +03:00
)
Users ( logins [ ] string ) (
2023-01-27 13:36:54 -05:00
[ ] * login . ExternalUserInfo , error ,
2019-05-17 14:57:26 +03:00
)
User ( login string ) (
2023-01-27 13:36:54 -05:00
* login . 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
2023-02-10 19:01:55 +01:00
cfg * setting . Cfg
log log . Logger
2019-05-17 14:57:26 +03:00
}
// New creates the new LDAP auth
2023-02-10 19:01:55 +01:00
func New ( configs [ ] * ldap . ServerConfig , cfg * setting . Cfg ) IMultiLDAP {
2019-05-17 14:57:26 +03:00
return & MultiLDAP {
configs : configs ,
2023-02-10 19:01:55 +01:00
cfg : cfg ,
log : log . New ( "ldap" ) ,
2019-05-17 14:57:26 +03:00
}
}
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
2023-02-10 19:01:55 +01:00
server := newLDAP ( config , multiples . cfg )
2019-09-04 15:29:14 +01:00
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
2023-01-27 13:36:54 -05:00
func ( multiples * MultiLDAP ) Login ( query * login . LoginUserQuery ) (
* login . ExternalUserInfo , error ,
2019-05-17 14:57:26 +03:00
) {
if len ( multiples . configs ) == 0 {
return nil , ErrNoLDAPServers
}
2022-12-14 09:41:51 +01:00
ldapSilentErrors := [ ] error { }
2019-11-05 08:58:59 +01:00
for index , config := range multiples . configs {
2023-02-10 19:01:55 +01:00
server := newLDAP ( config , multiples . cfg )
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 )
if err != nil {
2019-09-23 13:34:05 +01:00
if isSilentError ( err ) {
2022-12-14 09:41:51 +01:00
ldapSilentErrors = append ( ldapSilentErrors , err )
2023-02-10 19:01:55 +01:00
multiples . log . Debug (
2019-09-23 13:34:05 +01:00
"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
}
2022-12-14 09:41:51 +01:00
if user != nil {
return user , nil
}
}
// Return ErrInvalidCredentials in case any of the errors was ErrInvalidCredentials (means that the authentication has failed at least once)
for _ , ldapErr := range ldapSilentErrors {
if errors . Is ( ldapErr , ErrInvalidCredentials ) {
return nil , ErrInvalidCredentials
}
2019-05-17 14:57:26 +03:00
}
2022-12-14 09:41:51 +01:00
// Return ErrCouldNotFindUser if all of the configured LDAP servers returned with ErrCouldNotFindUser
return nil , ErrCouldNotFindUser
2019-05-17 14:57:26 +03:00
}
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 ) (
2023-01-27 13:36:54 -05:00
* login . 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 {
2023-02-10 19:01:55 +01:00
server := newLDAP ( config , multiples . cfg )
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 ) (
2023-01-27 13:36:54 -05:00
[ ] * login . ExternalUserInfo ,
2019-05-17 14:57:26 +03:00
error ,
) {
2023-01-27 13:36:54 -05:00
var result [ ] * login . ExternalUserInfo
2019-05-17 14:57:26 +03:00
if len ( multiples . configs ) == 0 {
return nil , ErrNoLDAPServers
}
2019-11-05 08:58:59 +01:00
for index , config := range multiples . configs {
2023-02-10 19:01:55 +01:00
server := newLDAP ( config , multiples . cfg )
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 ,
)
}