2019-04-26 07:47:16 -05:00
|
|
|
package ldap
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/tls"
|
|
|
|
"crypto/x509"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
LDAP: Divide the requests (#17885)
* LDAP: Divide the requests
Active Directory does indeed have a limitation with 1000 results
per search (default of course).
However, that limitation can be workaround with the pagination search feature,
meaning `pagination` number is how many times LDAP compatible server will be
requested by the client with specified amount of users (like 1000). That feature
already embeded with LDAP compatible client (including our `go-ldap`).
But slapd server has by default stricter settings. First, limitation is not 1000
but 500, second, pagination workaround presumably (information about it a bit
scarce and I still not sure on some of the details from my own testing)
cannot be workaround with pagination feature.
See
https://www.openldap.org/doc/admin24/limits.html
https://serverfault.com/questions/328671/paging-using-ldapsearch
hashicorp/vault#4162 - not sure why they were hitting the limit in
the first place, since `go-ldap` doesn't have one by default.
But, given all that, for me `ldapsearch` command with same request
as with `go-ldap` still returns more then 500 results, it can even return
as much as 10500 items (probably more).
So either there is some differences with implementation of the LDAP search
between `go-ldap` module and `ldapsearch` or I am missing a step :/.
In the wild (see serverfault link), apparently, people still hitting that
limitation even with `ldapsearch`, so it still seems to be an issue.
But, nevertheless, I'm still confused by this incoherence.
To workaround it, I divide the request by no more then
500 items per search
2019-07-03 09:39:54 -05:00
|
|
|
"math"
|
2020-10-22 00:34:26 -05:00
|
|
|
"net"
|
2022-08-10 08:37:51 -05:00
|
|
|
"os"
|
2020-10-22 00:34:26 -05:00
|
|
|
"strconv"
|
2019-04-26 07:47:16 -05:00
|
|
|
"strings"
|
2022-07-08 01:52:54 -05:00
|
|
|
"time"
|
2019-04-26 07:47:16 -05:00
|
|
|
|
2023-02-28 05:13:46 -06:00
|
|
|
"github.com/go-ldap/ldap/v3"
|
2021-09-14 03:49:37 -05:00
|
|
|
|
2019-05-13 01:45:54 -05:00
|
|
|
"github.com/grafana/grafana/pkg/infra/log"
|
2022-08-10 03:21:33 -05:00
|
|
|
"github.com/grafana/grafana/pkg/services/login"
|
2022-08-10 04:56:48 -05:00
|
|
|
"github.com/grafana/grafana/pkg/services/org"
|
2023-02-10 12:01:55 -06:00
|
|
|
"github.com/grafana/grafana/pkg/setting"
|
2019-04-26 07:47:16 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
// IConnection is interface for LDAP connection manipulation
|
|
|
|
type IConnection interface {
|
|
|
|
Bind(username, password string) error
|
|
|
|
UnauthenticatedBind(username string) error
|
2019-05-17 06:57:26 -05:00
|
|
|
Add(*ldap.AddRequest) error
|
|
|
|
Del(*ldap.DelRequest) error
|
|
|
|
Search(*ldap.SearchRequest) (*ldap.SearchResult, error)
|
2019-04-26 07:47:16 -05:00
|
|
|
StartTLS(*tls.Config) error
|
|
|
|
Close()
|
|
|
|
}
|
|
|
|
|
2019-05-17 06:57:26 -05:00
|
|
|
// IServer is interface for LDAP authorization
|
|
|
|
type IServer interface {
|
2023-01-27 12:36:54 -06:00
|
|
|
Login(*login.LoginUserQuery) (*login.ExternalUserInfo, error)
|
|
|
|
Users([]string) ([]*login.ExternalUserInfo, error)
|
2019-07-10 05:25:21 -05:00
|
|
|
Bind() error
|
|
|
|
UserBind(string, string) error
|
2019-05-17 06:57:26 -05:00
|
|
|
Dial() error
|
|
|
|
Close()
|
2019-04-26 07:47:16 -05:00
|
|
|
}
|
|
|
|
|
2019-05-17 06:57:26 -05:00
|
|
|
// Server is basic struct of LDAP authorization
|
|
|
|
type Server struct {
|
2023-02-10 12:01:55 -06:00
|
|
|
cfg *setting.Cfg
|
2019-06-13 09:47:52 -05:00
|
|
|
Config *ServerConfig
|
|
|
|
Connection IConnection
|
|
|
|
log log.Logger
|
2019-04-26 07:47:16 -05:00
|
|
|
}
|
|
|
|
|
2019-07-10 05:25:21 -05:00
|
|
|
// Bind authenticates the connection with the LDAP server
|
|
|
|
// - with the username and password setup in the config
|
|
|
|
// - or, anonymously
|
2019-09-16 08:13:35 -05:00
|
|
|
//
|
|
|
|
// Dial() sets the connection with the server for this Struct. Therefore, we require a
|
|
|
|
// call to Dial() before being able to execute this function.
|
2019-07-10 05:25:21 -05:00
|
|
|
func (server *Server) Bind() error {
|
2019-08-02 11:24:44 -05:00
|
|
|
if server.shouldAdminBind() {
|
|
|
|
if err := server.AdminBind(); err != nil {
|
2019-07-10 05:25:21 -05:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
err := server.Connection.UnauthenticatedBind(server.Config.BindDN)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
LDAP: Divide the requests (#17885)
* LDAP: Divide the requests
Active Directory does indeed have a limitation with 1000 results
per search (default of course).
However, that limitation can be workaround with the pagination search feature,
meaning `pagination` number is how many times LDAP compatible server will be
requested by the client with specified amount of users (like 1000). That feature
already embeded with LDAP compatible client (including our `go-ldap`).
But slapd server has by default stricter settings. First, limitation is not 1000
but 500, second, pagination workaround presumably (information about it a bit
scarce and I still not sure on some of the details from my own testing)
cannot be workaround with pagination feature.
See
https://www.openldap.org/doc/admin24/limits.html
https://serverfault.com/questions/328671/paging-using-ldapsearch
hashicorp/vault#4162 - not sure why they were hitting the limit in
the first place, since `go-ldap` doesn't have one by default.
But, given all that, for me `ldapsearch` command with same request
as with `go-ldap` still returns more then 500 results, it can even return
as much as 10500 items (probably more).
So either there is some differences with implementation of the LDAP search
between `go-ldap` module and `ldapsearch` or I am missing a step :/.
In the wild (see serverfault link), apparently, people still hitting that
limitation even with `ldapsearch`, so it still seems to be an issue.
But, nevertheless, I'm still confused by this incoherence.
To workaround it, I divide the request by no more then
500 items per search
2019-07-03 09:39:54 -05:00
|
|
|
// UsersMaxRequest is a max amount of users we can request via Users().
|
|
|
|
// Since many LDAP servers has limitations
|
|
|
|
// on how much items can we return in one request
|
|
|
|
const UsersMaxRequest = 500
|
|
|
|
|
2019-04-26 07:47:16 -05:00
|
|
|
var (
|
|
|
|
|
|
|
|
// ErrInvalidCredentials is returned if username and password do not match
|
2020-11-05 04:57:20 -06:00
|
|
|
ErrInvalidCredentials = errors.New("invalid username or password")
|
2019-07-05 09:49:00 -05:00
|
|
|
|
|
|
|
// ErrCouldNotFindUser is returned when username hasn't been found (not username+password)
|
2020-11-05 04:57:20 -06:00
|
|
|
ErrCouldNotFindUser = errors.New("can't find user in LDAP")
|
2019-04-26 07:47:16 -05:00
|
|
|
)
|
|
|
|
|
2019-08-02 11:24:44 -05:00
|
|
|
// New creates the new LDAP connection
|
2023-02-10 12:01:55 -06:00
|
|
|
func New(config *ServerConfig, cfg *setting.Cfg) IServer {
|
2019-05-17 06:57:26 -05:00
|
|
|
return &Server{
|
2019-05-27 02:36:49 -05:00
|
|
|
Config: config,
|
2023-02-10 12:01:55 -06:00
|
|
|
cfg: cfg,
|
2019-04-26 07:47:16 -05:00
|
|
|
log: log.New("ldap"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Dial dials in the LDAP
|
2019-08-02 11:24:44 -05:00
|
|
|
// TODO: decrease cyclomatic complexity
|
2019-05-17 06:57:26 -05:00
|
|
|
func (server *Server) Dial() error {
|
2019-04-26 07:47:16 -05:00
|
|
|
var err error
|
|
|
|
var certPool *x509.CertPool
|
2019-05-27 02:36:49 -05:00
|
|
|
if server.Config.RootCACert != "" {
|
2019-04-26 07:47:16 -05:00
|
|
|
certPool = x509.NewCertPool()
|
2019-05-27 02:36:49 -05:00
|
|
|
for _, caCertFile := range strings.Split(server.Config.RootCACert, " ") {
|
2020-12-03 15:13:06 -06:00
|
|
|
// nolint:gosec
|
|
|
|
// We can ignore the gosec G304 warning on this one because `caCertFile` comes from ldap config.
|
2022-08-10 08:37:51 -05:00
|
|
|
pem, err := os.ReadFile(caCertFile)
|
2019-04-26 07:47:16 -05:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if !certPool.AppendCertsFromPEM(pem) {
|
|
|
|
return errors.New("Failed to append CA certificate " + caCertFile)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
var clientCert tls.Certificate
|
2019-05-27 02:36:49 -05:00
|
|
|
if server.Config.ClientCert != "" && server.Config.ClientKey != "" {
|
|
|
|
clientCert, err = tls.LoadX509KeyPair(server.Config.ClientCert, server.Config.ClientKey)
|
2019-04-26 07:47:16 -05:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2022-07-08 01:52:54 -05:00
|
|
|
|
|
|
|
timeout := time.Duration(server.Config.Timeout) * time.Second
|
|
|
|
|
2019-05-27 02:36:49 -05:00
|
|
|
for _, host := range strings.Split(server.Config.Host, " ") {
|
2021-02-15 10:55:41 -06:00
|
|
|
// Remove any square brackets enclosing IPv6 addresses, a format we support for backwards compatibility
|
|
|
|
host = strings.TrimSuffix(strings.TrimPrefix(host, "["), "]")
|
2020-10-22 00:34:26 -05:00
|
|
|
address := net.JoinHostPort(host, strconv.Itoa(server.Config.Port))
|
2019-05-27 02:36:49 -05:00
|
|
|
if server.Config.UseSSL {
|
2019-04-26 07:47:16 -05:00
|
|
|
tlsCfg := &tls.Config{
|
2019-05-27 02:36:49 -05:00
|
|
|
InsecureSkipVerify: server.Config.SkipVerifySSL,
|
2019-04-26 07:47:16 -05:00
|
|
|
ServerName: host,
|
|
|
|
RootCAs: certPool,
|
2023-02-28 05:13:46 -06:00
|
|
|
MinVersion: server.Config.minTLSVersion,
|
|
|
|
CipherSuites: server.Config.tlsCiphers,
|
2019-04-26 07:47:16 -05:00
|
|
|
}
|
|
|
|
if len(clientCert.Certificate) > 0 {
|
|
|
|
tlsCfg.Certificates = append(tlsCfg.Certificates, clientCert)
|
|
|
|
}
|
2019-05-27 02:36:49 -05:00
|
|
|
if server.Config.StartTLS {
|
2022-07-08 01:52:54 -05:00
|
|
|
server.Connection, err = dialWithTimeout("tcp", address, timeout)
|
2019-04-26 07:47:16 -05:00
|
|
|
if err == nil {
|
2019-05-27 02:36:49 -05:00
|
|
|
if err = server.Connection.StartTLS(tlsCfg); err == nil {
|
2019-04-26 07:47:16 -05:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2022-07-08 01:52:54 -05:00
|
|
|
server.Connection, err = dialTLSWithTimeout("tcp", address, tlsCfg, timeout)
|
2019-04-26 07:47:16 -05:00
|
|
|
}
|
|
|
|
} else {
|
2022-07-08 01:52:54 -05:00
|
|
|
server.Connection, err = dialWithTimeout("tcp", address, timeout)
|
2019-04-26 07:47:16 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
if err == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-07-08 01:52:54 -05:00
|
|
|
// dialWithTimeout applies the specified timeout
|
|
|
|
// and connects to the given address on the given network using net.Dial
|
|
|
|
func dialWithTimeout(network, addr string, timeout time.Duration) (*ldap.Conn, error) {
|
|
|
|
c, err := net.DialTimeout(network, addr, timeout)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
conn := ldap.NewConn(c, false)
|
|
|
|
conn.Start()
|
|
|
|
return conn, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// dialTLSWithTimeout applies the specified timeout
|
|
|
|
// connects to the given address on the given network using tls.Dial
|
|
|
|
func dialTLSWithTimeout(network, addr string, config *tls.Config, timeout time.Duration) (*ldap.Conn, error) {
|
|
|
|
c, err := tls.DialWithDialer(&net.Dialer{Timeout: timeout}, network, addr, config)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
conn := ldap.NewConn(c, true)
|
|
|
|
conn.Start()
|
|
|
|
return conn, nil
|
|
|
|
}
|
|
|
|
|
2019-05-17 06:57:26 -05:00
|
|
|
// Close closes the LDAP connection
|
2019-09-16 08:13:35 -05:00
|
|
|
// Dial() sets the connection with the server for this Struct. Therefore, we require a
|
|
|
|
// call to Dial() before being able to execute this function.
|
2019-05-17 06:57:26 -05:00
|
|
|
func (server *Server) Close() {
|
2019-05-27 02:36:49 -05:00
|
|
|
server.Connection.Close()
|
2019-05-17 06:57:26 -05:00
|
|
|
}
|
2019-04-26 07:47:16 -05:00
|
|
|
|
2019-07-05 09:49:00 -05:00
|
|
|
// Login the user.
|
2019-08-02 11:24:44 -05:00
|
|
|
// 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
|
2020-04-29 14:37:21 -05:00
|
|
|
// and then search for the said user in order to retrieve all the information about them
|
2019-08-02 11:24:44 -05:00
|
|
|
// 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.
|
2019-09-16 08:13:35 -05:00
|
|
|
//
|
|
|
|
// Dial() sets the connection with the server for this Struct. Therefore, we require a
|
|
|
|
// call to Dial() before being able to execute this function.
|
2023-01-27 12:36:54 -06:00
|
|
|
func (server *Server) Login(query *login.LoginUserQuery) (
|
|
|
|
*login.ExternalUserInfo, error,
|
2019-05-17 06:57:26 -05:00
|
|
|
) {
|
2019-07-05 09:49:00 -05:00
|
|
|
var err error
|
2019-07-09 07:12:17 -05:00
|
|
|
var authAndBind bool
|
2019-07-05 09:49:00 -05:00
|
|
|
|
2019-07-09 07:12:17 -05:00
|
|
|
// Check if we can use a search user
|
2020-07-16 07:39:01 -05:00
|
|
|
switch {
|
|
|
|
case server.shouldAdminBind():
|
2019-08-02 11:24:44 -05:00
|
|
|
if err := server.AdminBind(); err != nil {
|
2019-07-05 09:49:00 -05:00
|
|
|
return nil, err
|
|
|
|
}
|
2020-07-16 07:39:01 -05:00
|
|
|
case server.shouldSingleBind():
|
2019-07-09 07:12:17 -05:00
|
|
|
authAndBind = true
|
2019-08-02 11:24:44 -05:00
|
|
|
err = server.UserBind(
|
|
|
|
server.singleBindDN(query.Username),
|
|
|
|
query.Password,
|
|
|
|
)
|
2019-07-09 07:12:17 -05:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-07-16 07:39:01 -05:00
|
|
|
default:
|
2019-07-05 09:49:00 -05:00
|
|
|
err := server.Connection.UnauthenticatedBind(server.Config.BindDN)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-04-26 07:47:16 -05:00
|
|
|
}
|
|
|
|
|
2019-05-17 06:57:26 -05:00
|
|
|
// Find user entry & attributes
|
|
|
|
users, err := server.Users([]string{query.Username})
|
2019-04-26 07:47:16 -05:00
|
|
|
if err != nil {
|
2019-05-17 06:57:26 -05:00
|
|
|
return nil, err
|
2019-04-26 07:47:16 -05:00
|
|
|
}
|
|
|
|
|
2019-05-17 06:57:26 -05:00
|
|
|
// If we couldn't find the user -
|
|
|
|
// we should show incorrect credentials err
|
|
|
|
if len(users) == 0 {
|
2019-07-05 09:49:00 -05:00
|
|
|
return nil, ErrCouldNotFindUser
|
2019-05-17 06:57:26 -05:00
|
|
|
}
|
2019-04-26 07:47:16 -05:00
|
|
|
|
2019-05-17 06:57:26 -05:00
|
|
|
user := users[0]
|
2019-05-29 08:12:42 -05:00
|
|
|
if err := server.validateGrafanaUser(user); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-07-09 07:12:17 -05:00
|
|
|
if !authAndBind {
|
|
|
|
// Authenticate user
|
2019-07-10 05:25:21 -05:00
|
|
|
err = server.UserBind(user.AuthId, query.Password)
|
2019-07-09 07:12:17 -05:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2019-07-05 09:49:00 -05:00
|
|
|
}
|
|
|
|
|
2019-05-17 06:57:26 -05:00
|
|
|
return user, nil
|
|
|
|
}
|
|
|
|
|
2019-08-02 11:24:44 -05:00
|
|
|
// shouldAdminBind checks if we should use
|
|
|
|
// admin username & password for LDAP bind
|
|
|
|
func (server *Server) shouldAdminBind() bool {
|
|
|
|
return server.Config.BindPassword != ""
|
|
|
|
}
|
|
|
|
|
|
|
|
// singleBindDN combines the bind with the username
|
|
|
|
// in order to get the proper path
|
2019-07-09 07:12:17 -05:00
|
|
|
func (server *Server) singleBindDN(username string) string {
|
|
|
|
return fmt.Sprintf(server.Config.BindDN, username)
|
|
|
|
}
|
|
|
|
|
2019-08-02 11:24:44 -05:00
|
|
|
// shouldSingleBind checks if we can use "single bind" approach
|
2019-07-09 07:12:17 -05:00
|
|
|
func (server *Server) shouldSingleBind() bool {
|
|
|
|
return strings.Contains(server.Config.BindDN, "%s")
|
|
|
|
}
|
|
|
|
|
2019-08-02 11:24:44 -05:00
|
|
|
// Users gets LDAP users by logins
|
2019-09-16 08:13:35 -05:00
|
|
|
// Dial() sets the connection with the server for this Struct. Therefore, we require a
|
|
|
|
// call to Dial() before being able to execute this function.
|
2019-05-17 06:57:26 -05:00
|
|
|
func (server *Server) Users(logins []string) (
|
2023-01-27 12:36:54 -06:00
|
|
|
[]*login.ExternalUserInfo,
|
2019-05-17 06:57:26 -05:00
|
|
|
error,
|
LDAP: Divide the requests (#17885)
* LDAP: Divide the requests
Active Directory does indeed have a limitation with 1000 results
per search (default of course).
However, that limitation can be workaround with the pagination search feature,
meaning `pagination` number is how many times LDAP compatible server will be
requested by the client with specified amount of users (like 1000). That feature
already embeded with LDAP compatible client (including our `go-ldap`).
But slapd server has by default stricter settings. First, limitation is not 1000
but 500, second, pagination workaround presumably (information about it a bit
scarce and I still not sure on some of the details from my own testing)
cannot be workaround with pagination feature.
See
https://www.openldap.org/doc/admin24/limits.html
https://serverfault.com/questions/328671/paging-using-ldapsearch
hashicorp/vault#4162 - not sure why they were hitting the limit in
the first place, since `go-ldap` doesn't have one by default.
But, given all that, for me `ldapsearch` command with same request
as with `go-ldap` still returns more then 500 results, it can even return
as much as 10500 items (probably more).
So either there is some differences with implementation of the LDAP search
between `go-ldap` module and `ldapsearch` or I am missing a step :/.
In the wild (see serverfault link), apparently, people still hitting that
limitation even with `ldapsearch`, so it still seems to be an issue.
But, nevertheless, I'm still confused by this incoherence.
To workaround it, I divide the request by no more then
500 items per search
2019-07-03 09:39:54 -05:00
|
|
|
) {
|
2021-09-14 03:49:37 -05:00
|
|
|
var users [][]*ldap.Entry
|
LDAP: Divide the requests (#17885)
* LDAP: Divide the requests
Active Directory does indeed have a limitation with 1000 results
per search (default of course).
However, that limitation can be workaround with the pagination search feature,
meaning `pagination` number is how many times LDAP compatible server will be
requested by the client with specified amount of users (like 1000). That feature
already embeded with LDAP compatible client (including our `go-ldap`).
But slapd server has by default stricter settings. First, limitation is not 1000
but 500, second, pagination workaround presumably (information about it a bit
scarce and I still not sure on some of the details from my own testing)
cannot be workaround with pagination feature.
See
https://www.openldap.org/doc/admin24/limits.html
https://serverfault.com/questions/328671/paging-using-ldapsearch
hashicorp/vault#4162 - not sure why they were hitting the limit in
the first place, since `go-ldap` doesn't have one by default.
But, given all that, for me `ldapsearch` command with same request
as with `go-ldap` still returns more then 500 results, it can even return
as much as 10500 items (probably more).
So either there is some differences with implementation of the LDAP search
between `go-ldap` module and `ldapsearch` or I am missing a step :/.
In the wild (see serverfault link), apparently, people still hitting that
limitation even with `ldapsearch`, so it still seems to be an issue.
But, nevertheless, I'm still confused by this incoherence.
To workaround it, I divide the request by no more then
500 items per search
2019-07-03 09:39:54 -05:00
|
|
|
err := getUsersIteration(logins, func(previous, current int) error {
|
2021-09-14 03:49:37 -05:00
|
|
|
var err error
|
|
|
|
users, err = server.users(logins[previous:current])
|
|
|
|
return err
|
LDAP: Divide the requests (#17885)
* LDAP: Divide the requests
Active Directory does indeed have a limitation with 1000 results
per search (default of course).
However, that limitation can be workaround with the pagination search feature,
meaning `pagination` number is how many times LDAP compatible server will be
requested by the client with specified amount of users (like 1000). That feature
already embeded with LDAP compatible client (including our `go-ldap`).
But slapd server has by default stricter settings. First, limitation is not 1000
but 500, second, pagination workaround presumably (information about it a bit
scarce and I still not sure on some of the details from my own testing)
cannot be workaround with pagination feature.
See
https://www.openldap.org/doc/admin24/limits.html
https://serverfault.com/questions/328671/paging-using-ldapsearch
hashicorp/vault#4162 - not sure why they were hitting the limit in
the first place, since `go-ldap` doesn't have one by default.
But, given all that, for me `ldapsearch` command with same request
as with `go-ldap` still returns more then 500 results, it can even return
as much as 10500 items (probably more).
So either there is some differences with implementation of the LDAP search
between `go-ldap` module and `ldapsearch` or I am missing a step :/.
In the wild (see serverfault link), apparently, people still hitting that
limitation even with `ldapsearch`, so it still seems to be an issue.
But, nevertheless, I'm still confused by this incoherence.
To workaround it, I divide the request by no more then
500 items per search
2019-07-03 09:39:54 -05:00
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(users) == 0 {
|
2023-01-27 12:36:54 -06:00
|
|
|
return []*login.ExternalUserInfo{}, nil
|
LDAP: Divide the requests (#17885)
* LDAP: Divide the requests
Active Directory does indeed have a limitation with 1000 results
per search (default of course).
However, that limitation can be workaround with the pagination search feature,
meaning `pagination` number is how many times LDAP compatible server will be
requested by the client with specified amount of users (like 1000). That feature
already embeded with LDAP compatible client (including our `go-ldap`).
But slapd server has by default stricter settings. First, limitation is not 1000
but 500, second, pagination workaround presumably (information about it a bit
scarce and I still not sure on some of the details from my own testing)
cannot be workaround with pagination feature.
See
https://www.openldap.org/doc/admin24/limits.html
https://serverfault.com/questions/328671/paging-using-ldapsearch
hashicorp/vault#4162 - not sure why they were hitting the limit in
the first place, since `go-ldap` doesn't have one by default.
But, given all that, for me `ldapsearch` command with same request
as with `go-ldap` still returns more then 500 results, it can even return
as much as 10500 items (probably more).
So either there is some differences with implementation of the LDAP search
between `go-ldap` module and `ldapsearch` or I am missing a step :/.
In the wild (see serverfault link), apparently, people still hitting that
limitation even with `ldapsearch`, so it still seems to be an issue.
But, nevertheless, I'm still confused by this incoherence.
To workaround it, I divide the request by no more then
500 items per search
2019-07-03 09:39:54 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
serializedUsers, err := server.serializeUsers(users)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-07-24 04:49:18 -05:00
|
|
|
server.log.Debug(
|
2023-07-10 04:14:51 -05:00
|
|
|
"LDAP users found", "users", fmt.Sprintf("%+v", serializedUsers),
|
2019-07-24 04:49:18 -05:00
|
|
|
)
|
LDAP: Divide the requests (#17885)
* LDAP: Divide the requests
Active Directory does indeed have a limitation with 1000 results
per search (default of course).
However, that limitation can be workaround with the pagination search feature,
meaning `pagination` number is how many times LDAP compatible server will be
requested by the client with specified amount of users (like 1000). That feature
already embeded with LDAP compatible client (including our `go-ldap`).
But slapd server has by default stricter settings. First, limitation is not 1000
but 500, second, pagination workaround presumably (information about it a bit
scarce and I still not sure on some of the details from my own testing)
cannot be workaround with pagination feature.
See
https://www.openldap.org/doc/admin24/limits.html
https://serverfault.com/questions/328671/paging-using-ldapsearch
hashicorp/vault#4162 - not sure why they were hitting the limit in
the first place, since `go-ldap` doesn't have one by default.
But, given all that, for me `ldapsearch` command with same request
as with `go-ldap` still returns more then 500 results, it can even return
as much as 10500 items (probably more).
So either there is some differences with implementation of the LDAP search
between `go-ldap` module and `ldapsearch` or I am missing a step :/.
In the wild (see serverfault link), apparently, people still hitting that
limitation even with `ldapsearch`, so it still seems to be an issue.
But, nevertheless, I'm still confused by this incoherence.
To workaround it, I divide the request by no more then
500 items per search
2019-07-03 09:39:54 -05:00
|
|
|
|
|
|
|
return serializedUsers, nil
|
|
|
|
}
|
|
|
|
|
2019-08-02 11:24:44 -05:00
|
|
|
// getUsersIteration is a helper function for Users() method.
|
|
|
|
// It divides the users by equal parts for the anticipated requests
|
|
|
|
func getUsersIteration(logins []string, fn func(int, int) error) error {
|
|
|
|
lenLogins := len(logins)
|
|
|
|
iterations := int(
|
|
|
|
math.Ceil(
|
|
|
|
float64(lenLogins) / float64(UsersMaxRequest),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
|
|
|
|
for i := 1; i < iterations+1; i++ {
|
|
|
|
previous := float64(UsersMaxRequest * (i - 1))
|
|
|
|
current := math.Min(float64(i*UsersMaxRequest), float64(lenLogins))
|
|
|
|
|
|
|
|
err := fn(int(previous), int(current))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
LDAP: Divide the requests (#17885)
* LDAP: Divide the requests
Active Directory does indeed have a limitation with 1000 results
per search (default of course).
However, that limitation can be workaround with the pagination search feature,
meaning `pagination` number is how many times LDAP compatible server will be
requested by the client with specified amount of users (like 1000). That feature
already embeded with LDAP compatible client (including our `go-ldap`).
But slapd server has by default stricter settings. First, limitation is not 1000
but 500, second, pagination workaround presumably (information about it a bit
scarce and I still not sure on some of the details from my own testing)
cannot be workaround with pagination feature.
See
https://www.openldap.org/doc/admin24/limits.html
https://serverfault.com/questions/328671/paging-using-ldapsearch
hashicorp/vault#4162 - not sure why they were hitting the limit in
the first place, since `go-ldap` doesn't have one by default.
But, given all that, for me `ldapsearch` command with same request
as with `go-ldap` still returns more then 500 results, it can even return
as much as 10500 items (probably more).
So either there is some differences with implementation of the LDAP search
between `go-ldap` module and `ldapsearch` or I am missing a step :/.
In the wild (see serverfault link), apparently, people still hitting that
limitation even with `ldapsearch`, so it still seems to be an issue.
But, nevertheless, I'm still confused by this incoherence.
To workaround it, I divide the request by no more then
500 items per search
2019-07-03 09:39:54 -05:00
|
|
|
// users is helper method for the Users()
|
|
|
|
func (server *Server) users(logins []string) (
|
2021-09-14 03:49:37 -05:00
|
|
|
[][]*ldap.Entry,
|
LDAP: Divide the requests (#17885)
* LDAP: Divide the requests
Active Directory does indeed have a limitation with 1000 results
per search (default of course).
However, that limitation can be workaround with the pagination search feature,
meaning `pagination` number is how many times LDAP compatible server will be
requested by the client with specified amount of users (like 1000). That feature
already embeded with LDAP compatible client (including our `go-ldap`).
But slapd server has by default stricter settings. First, limitation is not 1000
but 500, second, pagination workaround presumably (information about it a bit
scarce and I still not sure on some of the details from my own testing)
cannot be workaround with pagination feature.
See
https://www.openldap.org/doc/admin24/limits.html
https://serverfault.com/questions/328671/paging-using-ldapsearch
hashicorp/vault#4162 - not sure why they were hitting the limit in
the first place, since `go-ldap` doesn't have one by default.
But, given all that, for me `ldapsearch` command with same request
as with `go-ldap` still returns more then 500 results, it can even return
as much as 10500 items (probably more).
So either there is some differences with implementation of the LDAP search
between `go-ldap` module and `ldapsearch` or I am missing a step :/.
In the wild (see serverfault link), apparently, people still hitting that
limitation even with `ldapsearch`, so it still seems to be an issue.
But, nevertheless, I'm still confused by this incoherence.
To workaround it, I divide the request by no more then
500 items per search
2019-07-03 09:39:54 -05:00
|
|
|
error,
|
2019-05-17 06:57:26 -05:00
|
|
|
) {
|
|
|
|
var result *ldap.SearchResult
|
2019-05-27 02:36:49 -05:00
|
|
|
var Config = server.Config
|
2019-06-13 09:47:52 -05:00
|
|
|
var err error
|
2019-04-26 07:47:16 -05:00
|
|
|
|
2021-09-14 03:49:37 -05:00
|
|
|
var entries = make([][]*ldap.Entry, 0, len(Config.SearchBaseDNs))
|
|
|
|
|
2019-05-27 02:36:49 -05:00
|
|
|
for _, base := range Config.SearchBaseDNs {
|
|
|
|
result, err = server.Connection.Search(
|
2019-05-17 06:57:26 -05:00
|
|
|
server.getSearchRequest(base, logins),
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(result.Entries) > 0 {
|
2021-09-14 03:49:37 -05:00
|
|
|
entries = append(entries, result.Entries)
|
2019-05-17 06:57:26 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-14 03:49:37 -05:00
|
|
|
return entries, nil
|
2019-05-17 06:57:26 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// validateGrafanaUser validates user access.
|
|
|
|
// If there are no ldap group mappings access is true
|
|
|
|
// otherwise a single group must match
|
2023-01-27 12:36:54 -06:00
|
|
|
func (server *Server) validateGrafanaUser(user *login.ExternalUserInfo) error {
|
2023-02-10 12:01:55 -06:00
|
|
|
if !server.cfg.LDAPSkipOrgRoleSync && len(server.Config.Groups) > 0 &&
|
2022-10-12 06:33:33 -05:00
|
|
|
(len(user.OrgRoles) == 0 && (user.IsGrafanaAdmin == nil || !*user.IsGrafanaAdmin)) {
|
2023-02-10 12:01:55 -06:00
|
|
|
server.log.Warn(
|
2019-08-02 11:24:44 -05:00
|
|
|
"User does not belong in any of the specified LDAP groups",
|
2019-05-17 06:57:26 -05:00
|
|
|
"username", user.Login,
|
|
|
|
"groups", user.Groups,
|
|
|
|
)
|
|
|
|
return ErrInvalidCredentials
|
2019-04-26 07:47:16 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-05-17 06:57:26 -05:00
|
|
|
// getSearchRequest returns LDAP search request for users
|
|
|
|
func (server *Server) getSearchRequest(
|
|
|
|
base string,
|
|
|
|
logins []string,
|
|
|
|
) *ldap.SearchRequest {
|
|
|
|
attributes := []string{}
|
|
|
|
|
2019-05-27 02:36:49 -05:00
|
|
|
inputs := server.Config.Attr
|
2019-05-17 06:57:26 -05:00
|
|
|
attributes = appendIfNotEmpty(
|
|
|
|
attributes,
|
|
|
|
inputs.Username,
|
|
|
|
inputs.Surname,
|
|
|
|
inputs.Email,
|
|
|
|
inputs.Name,
|
|
|
|
inputs.MemberOf,
|
2019-07-24 04:49:18 -05:00
|
|
|
|
|
|
|
// In case for the POSIX LDAP schema server
|
|
|
|
server.Config.GroupSearchFilterUserAttribute,
|
2019-05-17 06:57:26 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
search := ""
|
|
|
|
for _, login := range logins {
|
2020-09-22 09:22:19 -05:00
|
|
|
query := strings.ReplaceAll(
|
2019-05-27 02:36:49 -05:00
|
|
|
server.Config.SearchFilter,
|
2019-05-17 06:57:26 -05:00
|
|
|
"%s", ldap.EscapeFilter(login),
|
|
|
|
)
|
|
|
|
|
2020-07-16 07:39:01 -05:00
|
|
|
search += query
|
2019-05-17 06:57:26 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
filter := fmt.Sprintf("(|%s)", search)
|
|
|
|
|
2020-04-24 02:53:42 -05:00
|
|
|
searchRequest := &ldap.SearchRequest{
|
2019-05-17 06:57:26 -05:00
|
|
|
BaseDN: base,
|
|
|
|
Scope: ldap.ScopeWholeSubtree,
|
|
|
|
DerefAliases: ldap.NeverDerefAliases,
|
|
|
|
Attributes: attributes,
|
|
|
|
Filter: filter,
|
|
|
|
}
|
2020-04-24 02:53:42 -05:00
|
|
|
|
|
|
|
server.log.Debug(
|
|
|
|
"LDAP SearchRequest", "searchRequest", fmt.Sprintf("%+v\n", searchRequest),
|
|
|
|
)
|
|
|
|
|
|
|
|
return searchRequest
|
2019-05-17 06:57:26 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// buildGrafanaUser extracts info from UserInfo model to ExternalUserInfo
|
2023-01-27 12:36:54 -06:00
|
|
|
func (server *Server) buildGrafanaUser(user *ldap.Entry) (*login.ExternalUserInfo, error) {
|
2019-06-13 09:47:52 -05:00
|
|
|
memberOf, err := server.getMemberOf(user)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
attrs := server.Config.Attr
|
2023-01-27 12:36:54 -06:00
|
|
|
extUser := &login.ExternalUserInfo{
|
2022-08-10 03:21:33 -05:00
|
|
|
AuthModule: login.LDAPAuthModule,
|
2019-04-26 07:47:16 -05:00
|
|
|
AuthId: user.DN,
|
2019-05-17 06:57:26 -05:00
|
|
|
Name: strings.TrimSpace(
|
2019-06-13 09:47:52 -05:00
|
|
|
fmt.Sprintf(
|
|
|
|
"%s %s",
|
|
|
|
getAttribute(attrs.Name, user),
|
|
|
|
getAttribute(attrs.Surname, user),
|
|
|
|
),
|
2019-05-17 06:57:26 -05:00
|
|
|
),
|
2019-06-13 09:47:52 -05:00
|
|
|
Login: getAttribute(attrs.Username, user),
|
|
|
|
Email: getAttribute(attrs.Email, user),
|
|
|
|
Groups: memberOf,
|
2022-08-10 04:56:48 -05:00
|
|
|
OrgRoles: map[int64]org.RoleType{},
|
2019-04-26 07:47:16 -05:00
|
|
|
}
|
|
|
|
|
2022-10-12 06:33:33 -05:00
|
|
|
// Skipping org role sync
|
2023-02-10 12:01:55 -06:00
|
|
|
if server.cfg.LDAPSkipOrgRoleSync {
|
2022-10-12 07:41:11 -05:00
|
|
|
server.log.Debug("skipping organization role mapping.")
|
2022-10-12 06:33:33 -05:00
|
|
|
return extUser, nil
|
|
|
|
}
|
|
|
|
|
2023-03-31 09:39:23 -05:00
|
|
|
isGrafanaAdmin := false
|
2019-05-27 02:36:49 -05:00
|
|
|
for _, group := range server.Config.Groups {
|
2019-04-26 07:47:16 -05:00
|
|
|
// only use the first match for each org
|
2019-09-19 10:13:38 -05:00
|
|
|
if extUser.OrgRoles[group.OrgId] != "" {
|
2019-04-26 07:47:16 -05:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2022-04-22 08:45:54 -05:00
|
|
|
if IsMemberOf(memberOf, group.GroupDN) {
|
2022-05-06 05:12:42 -05:00
|
|
|
if group.OrgRole != "" {
|
|
|
|
extUser.OrgRoles[group.OrgId] = group.OrgRole
|
|
|
|
}
|
|
|
|
|
2023-03-31 09:39:23 -05:00
|
|
|
if !isGrafanaAdmin && (group.IsGrafanaAdmin != nil && *group.IsGrafanaAdmin) {
|
|
|
|
isGrafanaAdmin = true
|
2019-04-26 07:47:16 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-03-31 09:39:23 -05:00
|
|
|
extUser.IsGrafanaAdmin = &isGrafanaAdmin
|
2019-04-26 07:47:16 -05:00
|
|
|
|
2020-07-31 07:41:31 -05:00
|
|
|
// If there are group org mappings configured, but no matching mappings,
|
|
|
|
// the user will not be able to login and will be disabled
|
2022-05-06 05:12:42 -05:00
|
|
|
if len(server.Config.Groups) > 0 && (len(extUser.OrgRoles) == 0 && (extUser.IsGrafanaAdmin == nil || !*extUser.IsGrafanaAdmin)) {
|
2020-07-31 07:41:31 -05:00
|
|
|
extUser.IsDisabled = true
|
|
|
|
}
|
|
|
|
|
2019-06-13 09:47:52 -05:00
|
|
|
return extUser, nil
|
2019-04-26 07:47:16 -05:00
|
|
|
}
|
|
|
|
|
2019-08-02 11:24:44 -05:00
|
|
|
// UserBind binds the user with the LDAP server
|
2019-09-16 08:13:35 -05:00
|
|
|
// Dial() sets the connection with the server for this Struct. Therefore, we require a
|
|
|
|
// call to Dial() before being able to execute this function.
|
2019-07-10 05:25:21 -05:00
|
|
|
func (server *Server) UserBind(username, password string) error {
|
|
|
|
err := server.userBind(username, password)
|
2019-07-05 09:49:00 -05:00
|
|
|
if err != nil {
|
|
|
|
server.log.Error(
|
2019-08-02 11:24:44 -05:00
|
|
|
fmt.Sprintf("Cannot bind user %s with LDAP", username),
|
2019-07-05 09:49:00 -05:00
|
|
|
"error",
|
|
|
|
err,
|
|
|
|
)
|
|
|
|
return err
|
2019-04-26 07:47:16 -05:00
|
|
|
}
|
|
|
|
|
2019-07-05 09:49:00 -05:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-08-02 11:24:44 -05:00
|
|
|
// AdminBind binds "admin" user with LDAP
|
2019-09-16 08:13:35 -05:00
|
|
|
// Dial() sets the connection with the server for this Struct. Therefore, we require a
|
|
|
|
// call to Dial() before being able to execute this function.
|
2019-08-02 11:24:44 -05:00
|
|
|
func (server *Server) AdminBind() error {
|
2019-07-10 05:25:21 -05:00
|
|
|
err := server.userBind(server.Config.BindDN, server.Config.BindPassword)
|
2019-07-05 09:49:00 -05:00
|
|
|
if err != nil {
|
|
|
|
server.log.Error(
|
2022-07-12 10:11:09 -05:00
|
|
|
"Cannot authenticate admin user in LDAP. Verify bind configuration",
|
2019-07-05 09:49:00 -05:00
|
|
|
"error",
|
|
|
|
err,
|
|
|
|
)
|
|
|
|
return err
|
2019-04-26 07:47:16 -05:00
|
|
|
}
|
|
|
|
|
2019-07-05 09:49:00 -05:00
|
|
|
return nil
|
|
|
|
}
|
2019-04-26 07:47:16 -05:00
|
|
|
|
2019-08-02 11:24:44 -05:00
|
|
|
// userBind binds the user with the LDAP server
|
2019-07-10 05:25:21 -05:00
|
|
|
func (server *Server) userBind(path, password string) error {
|
2019-07-05 09:49:00 -05:00
|
|
|
err := server.Connection.Bind(path, password)
|
|
|
|
if err != nil {
|
2020-11-19 07:47:17 -06:00
|
|
|
var ldapErr *ldap.Error
|
|
|
|
if errors.As(err, &ldapErr) && ldapErr.ResultCode == 49 {
|
|
|
|
return ErrInvalidCredentials
|
2019-04-26 07:47:16 -05:00
|
|
|
}
|
2020-11-19 07:47:17 -06:00
|
|
|
|
2019-04-26 07:47:16 -05:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-08-02 11:24:44 -05:00
|
|
|
// requestMemberOf use this function when POSIX LDAP
|
|
|
|
// schema does not support memberOf, so it manually search the groups
|
2019-06-13 09:47:52 -05:00
|
|
|
func (server *Server) requestMemberOf(entry *ldap.Entry) ([]string, error) {
|
2019-04-26 07:47:16 -05:00
|
|
|
var memberOf []string
|
2019-06-13 09:47:52 -05:00
|
|
|
var config = server.Config
|
2020-03-03 11:11:16 -06:00
|
|
|
var searchBaseDNs []string
|
2019-04-26 07:47:16 -05:00
|
|
|
|
2020-03-03 11:11:16 -06:00
|
|
|
if len(config.GroupSearchBaseDNs) > 0 {
|
|
|
|
searchBaseDNs = config.GroupSearchBaseDNs
|
|
|
|
} else {
|
|
|
|
searchBaseDNs = config.SearchBaseDNs
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, groupSearchBase := range searchBaseDNs {
|
2019-05-17 06:57:26 -05:00
|
|
|
var filterReplace string
|
2019-06-13 09:47:52 -05:00
|
|
|
if config.GroupSearchFilterUserAttribute == "" {
|
|
|
|
filterReplace = getAttribute(config.Attr.Username, entry)
|
2019-05-17 06:57:26 -05:00
|
|
|
} else {
|
2019-06-13 09:47:52 -05:00
|
|
|
filterReplace = getAttribute(
|
|
|
|
config.GroupSearchFilterUserAttribute,
|
|
|
|
entry,
|
|
|
|
)
|
2019-04-26 07:47:16 -05:00
|
|
|
}
|
|
|
|
|
2020-09-22 09:22:19 -05:00
|
|
|
filter := strings.ReplaceAll(
|
2019-06-13 09:47:52 -05:00
|
|
|
config.GroupSearchFilter, "%s",
|
2019-05-17 06:57:26 -05:00
|
|
|
ldap.EscapeFilter(filterReplace),
|
2019-04-26 07:47:16 -05:00
|
|
|
)
|
|
|
|
|
2019-05-17 06:57:26 -05:00
|
|
|
server.log.Info("Searching for user's groups", "filter", filter)
|
|
|
|
|
|
|
|
// support old way of reading settings
|
2019-06-13 09:47:52 -05:00
|
|
|
groupIDAttribute := config.Attr.MemberOf
|
2019-05-17 06:57:26 -05:00
|
|
|
// but prefer dn attribute if default settings are used
|
|
|
|
if groupIDAttribute == "" || groupIDAttribute == "memberOf" {
|
|
|
|
groupIDAttribute = "dn"
|
|
|
|
}
|
2019-04-26 07:47:16 -05:00
|
|
|
|
2019-05-17 06:57:26 -05:00
|
|
|
groupSearchReq := ldap.SearchRequest{
|
|
|
|
BaseDN: groupSearchBase,
|
|
|
|
Scope: ldap.ScopeWholeSubtree,
|
|
|
|
DerefAliases: ldap.NeverDerefAliases,
|
|
|
|
Attributes: []string{groupIDAttribute},
|
|
|
|
Filter: filter,
|
2019-04-26 07:47:16 -05:00
|
|
|
}
|
|
|
|
|
2019-05-27 02:36:49 -05:00
|
|
|
groupSearchResult, err := server.Connection.Search(&groupSearchReq)
|
2019-04-26 07:47:16 -05:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2019-05-17 06:57:26 -05:00
|
|
|
if len(groupSearchResult.Entries) > 0 {
|
2019-06-13 09:47:52 -05:00
|
|
|
for _, group := range groupSearchResult.Entries {
|
|
|
|
memberOf = append(
|
|
|
|
memberOf,
|
|
|
|
getAttribute(groupIDAttribute, group),
|
|
|
|
)
|
2019-05-17 06:57:26 -05:00
|
|
|
}
|
2019-04-26 07:47:16 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-17 06:57:26 -05:00
|
|
|
return memberOf, nil
|
2019-04-26 07:47:16 -05:00
|
|
|
}
|
|
|
|
|
2019-05-17 06:57:26 -05:00
|
|
|
// serializeUsers serializes the users
|
|
|
|
// from LDAP result to ExternalInfo struct
|
|
|
|
func (server *Server) serializeUsers(
|
2021-09-14 03:49:37 -05:00
|
|
|
entries [][]*ldap.Entry,
|
2023-01-27 12:36:54 -06:00
|
|
|
) ([]*login.ExternalUserInfo, error) {
|
|
|
|
var serialized []*login.ExternalUserInfo
|
2021-09-14 03:49:37 -05:00
|
|
|
var users = map[string]struct{}{}
|
2019-04-26 07:47:16 -05:00
|
|
|
|
2021-09-14 03:49:37 -05:00
|
|
|
for _, dn := range entries {
|
|
|
|
for _, user := range dn {
|
|
|
|
extUser, err := server.buildGrafanaUser(user)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, exists := users[extUser.Login]; exists {
|
|
|
|
// ignore duplicates
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
users[extUser.Login] = struct{}{}
|
2019-05-17 06:57:26 -05:00
|
|
|
|
2021-09-14 03:49:37 -05:00
|
|
|
serialized = append(serialized, extUser)
|
|
|
|
}
|
2019-05-17 06:57:26 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return serialized, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// getMemberOf finds memberOf property or request it
|
2019-06-13 09:47:52 -05:00
|
|
|
func (server *Server) getMemberOf(result *ldap.Entry) (
|
2019-05-17 06:57:26 -05:00
|
|
|
[]string, error,
|
|
|
|
) {
|
2019-05-27 02:36:49 -05:00
|
|
|
if server.Config.GroupSearchFilter == "" {
|
2019-06-13 09:47:52 -05:00
|
|
|
memberOf := getArrayAttribute(server.Config.Attr.MemberOf, result)
|
2019-05-17 06:57:26 -05:00
|
|
|
|
|
|
|
return memberOf, nil
|
|
|
|
}
|
|
|
|
|
2019-06-13 09:47:52 -05:00
|
|
|
memberOf, err := server.requestMemberOf(result)
|
2019-05-17 06:57:26 -05:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2019-04-26 07:47:16 -05:00
|
|
|
}
|
|
|
|
|
2019-05-17 06:57:26 -05:00
|
|
|
return memberOf, nil
|
2019-04-26 07:47:16 -05:00
|
|
|
}
|