package service import ( "context" "errors" "fmt" "sync" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/login/social" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/ldap" "github.com/grafana/grafana/pkg/services/ldap/multildap" "github.com/grafana/grafana/pkg/services/login" "github.com/grafana/grafana/pkg/services/ssosettings" "github.com/grafana/grafana/pkg/services/ssosettings/models" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" ) var ( ErrUnableToCreateLDAPClient = errors.New("unable to create LDAP client") ErrLDAPNotEnabled = errors.New("LDAP not enabled") ) // LDAP is the interface for the LDAP service. type LDAP interface { ReloadConfig() error Config() *ldap.ServersConfig Client() multildap.IMultiLDAP // Login authenticates the user against the LDAP server. Login(query *login.LoginUserQuery) (*login.ExternalUserInfo, error) // User searches for a user in the LDAP server. User(username string) (*login.ExternalUserInfo, error) } type LDAPImpl struct { client multildap.IMultiLDAP cfg *ldap.Config ldapCfg *ldap.ServersConfig log log.Logger features featuremgmt.FeatureToggles ssoSettings ssosettings.Service // loadingMutex locks the reading of the config so multiple requests for reloading are sequential. loadingMutex *sync.Mutex } func ProvideService(cfg *setting.Cfg, features featuremgmt.FeatureToggles, ssoSettings ssosettings.Service) *LDAPImpl { s := &LDAPImpl{ log: log.New("ldap.service"), loadingMutex: &sync.Mutex{}, features: features, ssoSettings: ssoSettings, } if s.features.IsEnabledGlobally(featuremgmt.FlagSsoSettingsApi) && s.features.IsEnabledGlobally(featuremgmt.FlagSsoSettingsLDAP) { s.ssoSettings.RegisterReloadable(social.LDAPProviderName, s) ldapSettings, err := s.ssoSettings.GetForProvider(context.Background(), social.LDAPProviderName) if err != nil { s.log.Error("Failed to retrieve LDAP settings from SSO settings service", "error", err) return s } err = s.Reload(context.Background(), *ldapSettings) if err != nil { s.log.Error("Failed to load LDAP settings", "error", err) return s } } else { s.cfg = ldap.GetLDAPConfig(cfg) if !cfg.LDAPAuthEnabled { return s } ldapCfg, err := multildap.GetConfig(s.cfg) if err != nil { s.log.Error("Failed to get LDAP config", "error", err) } else { s.ldapCfg = ldapCfg s.client = multildap.New(s.ldapCfg.Servers, s.cfg) } } return s } func (s *LDAPImpl) Reload(ctx context.Context, settings models.SSOSettings) error { cfg := &ldap.Config{} cfg.Enabled = resolveBool(settings.Settings["enabled"], false) cfg.SkipOrgRoleSync = resolveBool(settings.Settings["skip_org_role_sync"], false) cfg.AllowSignUp = resolveBool(settings.Settings["allow_sign_up"], true) ldapCfg, err := resolveServerConfig(settings.Settings["config"]) if err != nil { return err } // calculate MinTLSVersionID and TLSCipherIDs from input text values // also initialize Timeout and OrgID from group mappings with default values if they are not configured for _, server := range ldapCfg.Servers { if server.MinTLSVersion != "" { server.MinTLSVersionID, err = util.TlsNameToVersion(server.MinTLSVersion) if err != nil { s.log.Error("failed to set min TLS version, ignoring", "err", err, "server", server.Host) } } if len(server.TLSCiphers) > 0 { server.TLSCipherIDs, err = util.TlsCiphersToIDs(server.TLSCiphers) if err != nil { s.log.Error("unrecognized TLS Cipher(s), ignoring", "err", err, "server", server.Host) } } for _, groupMap := range server.Groups { if groupMap.OrgId == 0 { groupMap.OrgId = 1 } } if server.Timeout == 0 { server.Timeout = ldap.DefaultTimeout } } s.loadingMutex.Lock() defer s.loadingMutex.Unlock() s.cfg = cfg s.ldapCfg = ldapCfg s.client = multildap.New(s.ldapCfg.Servers, s.cfg) return nil } func (s *LDAPImpl) Validate(ctx context.Context, settings models.SSOSettings, oldSettings models.SSOSettings, requester identity.Requester) error { ldapCfg, err := resolveServerConfig(settings.Settings["config"]) if err != nil { return err } enabled := resolveBool(settings.Settings["enabled"], false) if !enabled { return nil } if len(ldapCfg.Servers) == 0 { return fmt.Errorf("no servers configured for LDAP") } for i, server := range ldapCfg.Servers { // host is required for every LDAP server config if server.Host == "" { return fmt.Errorf("no host configured for server with index %d", i) } if server.SearchFilter == "" { return fmt.Errorf("no search filter configured for server with index %d", i) } if len(server.SearchBaseDNs) == 0 { return fmt.Errorf("no search base DN configured for server with index %d", i) } if server.MinTLSVersion != "" { _, err = util.TlsNameToVersion(server.MinTLSVersion) if err != nil { return fmt.Errorf("invalid min TLS version configured for server with index %d", i) } } if len(server.TLSCiphers) > 0 { _, err = util.TlsCiphersToIDs(server.TLSCiphers) if err != nil { return fmt.Errorf("invalid TLS ciphers configured for server with index %d", i) } } for _, groupMap := range server.Groups { if groupMap.OrgRole == "" && groupMap.IsGrafanaAdmin == nil { return fmt.Errorf("organization role or Grafana admin status is required in group mappings for server with index %d", i) } } } return nil } func (s *LDAPImpl) ReloadConfig() error { if !s.cfg.Enabled { return nil } s.loadingMutex.Lock() defer s.loadingMutex.Unlock() config, err := ldap.GetConfig(s.cfg) if err != nil { return err } client := multildap.New(config.Servers, s.cfg) if client == nil { return ErrUnableToCreateLDAPClient } s.ldapCfg = config s.client = client return nil } func (s *LDAPImpl) Client() multildap.IMultiLDAP { return s.client } func (s *LDAPImpl) Config() *ldap.ServersConfig { return s.ldapCfg } func (s *LDAPImpl) Login(query *login.LoginUserQuery) (*login.ExternalUserInfo, error) { if !s.cfg.Enabled { return nil, ErrLDAPNotEnabled } client := s.Client() if client == nil { return nil, ErrUnableToCreateLDAPClient } return client.Login(query) } func (s *LDAPImpl) User(username string) (*login.ExternalUserInfo, error) { if !s.cfg.Enabled { return nil, ErrLDAPNotEnabled } client := s.Client() if client == nil { return nil, ErrUnableToCreateLDAPClient } user, _, err := client.User(username) return user, err }