mirror of https://github.com/grafana/grafana.git
LDAP: Move LDAP globals to Config (#63255)
* structure dtos and private methods * add basic LDAP service * use LDAP service in ldap debug API * lower non fatal error * remove unused globals * wip * remove final globals * fix tests to use cfg enabled * restructure errors * remove logger from globals * use ldap service in authn * use ldap service in context handler * fix failed tests * fix ldap middleware provides * fix provides in auth_test.go
This commit is contained in:
parent
8520a8614c
commit
d4cfbd9fd3
|
|
@ -43,6 +43,7 @@ import (
|
||||||
"github.com/grafana/grafana/pkg/services/folder/folderimpl"
|
"github.com/grafana/grafana/pkg/services/folder/folderimpl"
|
||||||
"github.com/grafana/grafana/pkg/services/folder/foldertest"
|
"github.com/grafana/grafana/pkg/services/folder/foldertest"
|
||||||
"github.com/grafana/grafana/pkg/services/guardian"
|
"github.com/grafana/grafana/pkg/services/guardian"
|
||||||
|
"github.com/grafana/grafana/pkg/services/ldap/service"
|
||||||
"github.com/grafana/grafana/pkg/services/licensing"
|
"github.com/grafana/grafana/pkg/services/licensing"
|
||||||
"github.com/grafana/grafana/pkg/services/login"
|
"github.com/grafana/grafana/pkg/services/login"
|
||||||
"github.com/grafana/grafana/pkg/services/login/loginservice"
|
"github.com/grafana/grafana/pkg/services/login/loginservice"
|
||||||
|
|
@ -218,7 +219,7 @@ func getContextHandler(t *testing.T, cfg *setting.Cfg) *contexthandler.ContextHa
|
||||||
renderSvc := &fakeRenderService{}
|
renderSvc := &fakeRenderService{}
|
||||||
authJWTSvc := jwt.NewFakeJWTService()
|
authJWTSvc := jwt.NewFakeJWTService()
|
||||||
tracer := tracing.InitializeTracerForTest()
|
tracer := tracing.InitializeTracerForTest()
|
||||||
authProxy := authproxy.ProvideAuthProxy(cfg, remoteCacheSvc, loginservice.LoginServiceMock{}, &usertest.FakeUserService{}, sqlStore)
|
authProxy := authproxy.ProvideAuthProxy(cfg, remoteCacheSvc, loginservice.LoginServiceMock{}, &usertest.FakeUserService{}, sqlStore, service.NewLDAPFakeService())
|
||||||
loginService := &logintest.LoginServiceFake{}
|
loginService := &logintest.LoginServiceFake{}
|
||||||
authenticator := &logintest.AuthenticatorFake{}
|
authenticator := &logintest.AuthenticatorFake{}
|
||||||
ctxHdlr := contexthandler.ProvideService(cfg, userAuthTokenSvc, authJWTSvc, remoteCacheSvc, renderSvc, sqlStore, tracer, authProxy, loginService, nil, authenticator, usertest.NewUserServiceFake(), orgtest.NewOrgServiceFake(), nil, featuremgmt.WithFeatures(), &authntest.FakeService{})
|
ctxHdlr := contexthandler.ProvideService(cfg, userAuthTokenSvc, authJWTSvc, remoteCacheSvc, renderSvc, sqlStore, tracer, authProxy, loginService, nil, authenticator, usertest.NewUserServiceFake(), orgtest.NewOrgServiceFake(), nil, featuremgmt.WithFeatures(), &authntest.FakeService{})
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"github.com/grafana/grafana/pkg/services/login"
|
"github.com/grafana/grafana/pkg/services/login"
|
||||||
"github.com/grafana/grafana/pkg/services/loginattempt"
|
"github.com/grafana/grafana/pkg/services/loginattempt"
|
||||||
"github.com/grafana/grafana/pkg/services/user"
|
"github.com/grafana/grafana/pkg/services/user"
|
||||||
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -36,13 +37,17 @@ type AuthenticatorService struct {
|
||||||
loginService login.Service
|
loginService login.Service
|
||||||
loginAttemptService loginattempt.Service
|
loginAttemptService loginattempt.Service
|
||||||
userService user.Service
|
userService user.Service
|
||||||
|
cfg *setting.Cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
func ProvideService(store db.DB, loginService login.Service, loginAttemptService loginattempt.Service, userService user.Service) *AuthenticatorService {
|
func ProvideService(store db.DB, loginService login.Service,
|
||||||
|
loginAttemptService loginattempt.Service,
|
||||||
|
userService user.Service, cfg *setting.Cfg) *AuthenticatorService {
|
||||||
a := &AuthenticatorService{
|
a := &AuthenticatorService{
|
||||||
loginService: loginService,
|
loginService: loginService,
|
||||||
loginAttemptService: loginAttemptService,
|
loginAttemptService: loginAttemptService,
|
||||||
userService: userService,
|
userService: userService,
|
||||||
|
cfg: cfg,
|
||||||
}
|
}
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
@ -73,7 +78,7 @@ func (a *AuthenticatorService) AuthenticateUser(ctx context.Context, query *logi
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ldapEnabled, ldapErr := loginUsingLDAP(ctx, query, a.loginService)
|
ldapEnabled, ldapErr := loginUsingLDAP(ctx, query, a.loginService, a.cfg)
|
||||||
if ldapEnabled {
|
if ldapEnabled {
|
||||||
query.AuthModule = login.LDAPAuthModule
|
query.AuthModule = login.LDAPAuthModule
|
||||||
if ldapErr == nil || !errors.Is(ldapErr, ldap.ErrInvalidCredentials) {
|
if ldapErr == nil || !errors.Is(ldapErr, ldap.ErrInvalidCredentials) {
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,8 @@ func TestAuthenticateUser(t *testing.T) {
|
||||||
mockLoginUsingLDAP(false, nil, sc)
|
mockLoginUsingLDAP(false, nil, sc)
|
||||||
|
|
||||||
loginAttemptService := &loginattempttest.FakeLoginAttemptService{ExpectedValid: true}
|
loginAttemptService := &loginattempttest.FakeLoginAttemptService{ExpectedValid: true}
|
||||||
a := AuthenticatorService{loginAttemptService: loginAttemptService, loginService: &logintest.LoginServiceFake{}}
|
cfg := setting.NewCfg()
|
||||||
|
a := AuthenticatorService{loginAttemptService: loginAttemptService, loginService: &logintest.LoginServiceFake{}, cfg: cfg}
|
||||||
err := a.AuthenticateUser(context.Background(), &login.LoginUserQuery{
|
err := a.AuthenticateUser(context.Background(), &login.LoginUserQuery{
|
||||||
Username: "user",
|
Username: "user",
|
||||||
Password: "",
|
Password: "",
|
||||||
|
|
@ -35,10 +36,11 @@ func TestAuthenticateUser(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
authScenario(t, "When user authenticates with no auth provider enabled", func(sc *authScenarioContext) {
|
authScenario(t, "When user authenticates with no auth provider enabled", func(sc *authScenarioContext) {
|
||||||
|
cfg := setting.NewCfg()
|
||||||
sc.loginUserQuery.Cfg.DisableLogin = true
|
sc.loginUserQuery.Cfg.DisableLogin = true
|
||||||
|
|
||||||
loginAttemptService := &loginattempttest.MockLoginAttemptService{ExpectedValid: true}
|
loginAttemptService := &loginattempttest.MockLoginAttemptService{ExpectedValid: true}
|
||||||
a := AuthenticatorService{loginAttemptService: loginAttemptService, loginService: &logintest.LoginServiceFake{}}
|
a := AuthenticatorService{loginAttemptService: loginAttemptService, loginService: &logintest.LoginServiceFake{}, cfg: cfg}
|
||||||
err := a.AuthenticateUser(context.Background(), sc.loginUserQuery)
|
err := a.AuthenticateUser(context.Background(), sc.loginUserQuery)
|
||||||
|
|
||||||
require.EqualError(t, err, ErrNoAuthProvider.Error())
|
require.EqualError(t, err, ErrNoAuthProvider.Error())
|
||||||
|
|
@ -50,11 +52,12 @@ func TestAuthenticateUser(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
authScenario(t, "When a user authenticates having too many login attempts", func(sc *authScenarioContext) {
|
authScenario(t, "When a user authenticates having too many login attempts", func(sc *authScenarioContext) {
|
||||||
|
cfg := setting.NewCfg()
|
||||||
mockLoginUsingGrafanaDB(nil, sc)
|
mockLoginUsingGrafanaDB(nil, sc)
|
||||||
mockLoginUsingLDAP(true, nil, sc)
|
mockLoginUsingLDAP(true, nil, sc)
|
||||||
|
|
||||||
loginAttemptService := &loginattempttest.MockLoginAttemptService{ExpectedValid: false}
|
loginAttemptService := &loginattempttest.MockLoginAttemptService{ExpectedValid: false}
|
||||||
a := AuthenticatorService{loginAttemptService: loginAttemptService, loginService: &logintest.LoginServiceFake{}}
|
a := AuthenticatorService{loginAttemptService: loginAttemptService, loginService: &logintest.LoginServiceFake{}, cfg: cfg}
|
||||||
err := a.AuthenticateUser(context.Background(), sc.loginUserQuery)
|
err := a.AuthenticateUser(context.Background(), sc.loginUserQuery)
|
||||||
|
|
||||||
require.EqualError(t, err, ErrTooManyLoginAttempts.Error())
|
require.EqualError(t, err, ErrTooManyLoginAttempts.Error())
|
||||||
|
|
@ -66,11 +69,12 @@ func TestAuthenticateUser(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
authScenario(t, "When grafana user authenticate with valid credentials", func(sc *authScenarioContext) {
|
authScenario(t, "When grafana user authenticate with valid credentials", func(sc *authScenarioContext) {
|
||||||
|
cfg := setting.NewCfg()
|
||||||
mockLoginUsingGrafanaDB(nil, sc)
|
mockLoginUsingGrafanaDB(nil, sc)
|
||||||
mockLoginUsingLDAP(true, ErrInvalidCredentials, sc)
|
mockLoginUsingLDAP(true, ErrInvalidCredentials, sc)
|
||||||
|
|
||||||
loginAttemptService := &loginattempttest.MockLoginAttemptService{ExpectedValid: true}
|
loginAttemptService := &loginattempttest.MockLoginAttemptService{ExpectedValid: true}
|
||||||
a := AuthenticatorService{loginAttemptService: loginAttemptService, loginService: &logintest.LoginServiceFake{}}
|
a := AuthenticatorService{loginAttemptService: loginAttemptService, loginService: &logintest.LoginServiceFake{}, cfg: cfg}
|
||||||
err := a.AuthenticateUser(context.Background(), sc.loginUserQuery)
|
err := a.AuthenticateUser(context.Background(), sc.loginUserQuery)
|
||||||
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -82,12 +86,13 @@ func TestAuthenticateUser(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
authScenario(t, "When grafana user authenticate and unexpected error occurs", func(sc *authScenarioContext) {
|
authScenario(t, "When grafana user authenticate and unexpected error occurs", func(sc *authScenarioContext) {
|
||||||
|
cfg := setting.NewCfg()
|
||||||
customErr := errors.New("custom")
|
customErr := errors.New("custom")
|
||||||
mockLoginUsingGrafanaDB(customErr, sc)
|
mockLoginUsingGrafanaDB(customErr, sc)
|
||||||
mockLoginUsingLDAP(true, ErrInvalidCredentials, sc)
|
mockLoginUsingLDAP(true, ErrInvalidCredentials, sc)
|
||||||
|
|
||||||
loginAttemptService := &loginattempttest.MockLoginAttemptService{ExpectedValid: true}
|
loginAttemptService := &loginattempttest.MockLoginAttemptService{ExpectedValid: true}
|
||||||
a := AuthenticatorService{loginAttemptService: loginAttemptService, loginService: &logintest.LoginServiceFake{}}
|
a := AuthenticatorService{loginAttemptService: loginAttemptService, loginService: &logintest.LoginServiceFake{}, cfg: cfg}
|
||||||
err := a.AuthenticateUser(context.Background(), sc.loginUserQuery)
|
err := a.AuthenticateUser(context.Background(), sc.loginUserQuery)
|
||||||
|
|
||||||
require.EqualError(t, err, customErr.Error())
|
require.EqualError(t, err, customErr.Error())
|
||||||
|
|
@ -99,11 +104,12 @@ func TestAuthenticateUser(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
authScenario(t, "When a non-existing grafana user authenticate and ldap disabled", func(sc *authScenarioContext) {
|
authScenario(t, "When a non-existing grafana user authenticate and ldap disabled", func(sc *authScenarioContext) {
|
||||||
|
cfg := setting.NewCfg()
|
||||||
mockLoginUsingGrafanaDB(user.ErrUserNotFound, sc)
|
mockLoginUsingGrafanaDB(user.ErrUserNotFound, sc)
|
||||||
mockLoginUsingLDAP(false, nil, sc)
|
mockLoginUsingLDAP(false, nil, sc)
|
||||||
|
|
||||||
loginAttemptService := &loginattempttest.MockLoginAttemptService{ExpectedValid: true}
|
loginAttemptService := &loginattempttest.MockLoginAttemptService{ExpectedValid: true}
|
||||||
a := AuthenticatorService{loginAttemptService: loginAttemptService, loginService: &logintest.LoginServiceFake{}}
|
a := AuthenticatorService{loginAttemptService: loginAttemptService, loginService: &logintest.LoginServiceFake{}, cfg: cfg}
|
||||||
err := a.AuthenticateUser(context.Background(), sc.loginUserQuery)
|
err := a.AuthenticateUser(context.Background(), sc.loginUserQuery)
|
||||||
|
|
||||||
require.EqualError(t, err, user.ErrUserNotFound.Error())
|
require.EqualError(t, err, user.ErrUserNotFound.Error())
|
||||||
|
|
@ -115,11 +121,13 @@ func TestAuthenticateUser(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
authScenario(t, "When a non-existing grafana user authenticate and invalid ldap credentials", func(sc *authScenarioContext) {
|
authScenario(t, "When a non-existing grafana user authenticate and invalid ldap credentials", func(sc *authScenarioContext) {
|
||||||
|
cfg := setting.NewCfg()
|
||||||
|
cfg.LDAPEnabled = true
|
||||||
mockLoginUsingGrafanaDB(user.ErrUserNotFound, sc)
|
mockLoginUsingGrafanaDB(user.ErrUserNotFound, sc)
|
||||||
mockLoginUsingLDAP(true, ldap.ErrInvalidCredentials, sc)
|
mockLoginUsingLDAP(true, ldap.ErrInvalidCredentials, sc)
|
||||||
|
|
||||||
loginAttemptService := &loginattempttest.MockLoginAttemptService{ExpectedValid: true}
|
loginAttemptService := &loginattempttest.MockLoginAttemptService{ExpectedValid: true}
|
||||||
a := AuthenticatorService{loginAttemptService: loginAttemptService, loginService: &logintest.LoginServiceFake{}}
|
a := AuthenticatorService{loginAttemptService: loginAttemptService, loginService: &logintest.LoginServiceFake{}, cfg: cfg}
|
||||||
err := a.AuthenticateUser(context.Background(), sc.loginUserQuery)
|
err := a.AuthenticateUser(context.Background(), sc.loginUserQuery)
|
||||||
|
|
||||||
require.EqualError(t, err, ErrInvalidCredentials.Error())
|
require.EqualError(t, err, ErrInvalidCredentials.Error())
|
||||||
|
|
@ -131,11 +139,13 @@ func TestAuthenticateUser(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
authScenario(t, "When a non-existing grafana user authenticate and valid ldap credentials", func(sc *authScenarioContext) {
|
authScenario(t, "When a non-existing grafana user authenticate and valid ldap credentials", func(sc *authScenarioContext) {
|
||||||
|
cfg := setting.NewCfg()
|
||||||
|
cfg.LDAPEnabled = true
|
||||||
mockLoginUsingGrafanaDB(user.ErrUserNotFound, sc)
|
mockLoginUsingGrafanaDB(user.ErrUserNotFound, sc)
|
||||||
mockLoginUsingLDAP(true, nil, sc)
|
mockLoginUsingLDAP(true, nil, sc)
|
||||||
|
|
||||||
loginAttemptService := &loginattempttest.MockLoginAttemptService{ExpectedValid: true}
|
loginAttemptService := &loginattempttest.MockLoginAttemptService{ExpectedValid: true}
|
||||||
a := AuthenticatorService{loginAttemptService: loginAttemptService, loginService: &logintest.LoginServiceFake{}}
|
a := AuthenticatorService{loginAttemptService: loginAttemptService, loginService: &logintest.LoginServiceFake{}, cfg: cfg}
|
||||||
err := a.AuthenticateUser(context.Background(), sc.loginUserQuery)
|
err := a.AuthenticateUser(context.Background(), sc.loginUserQuery)
|
||||||
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -147,12 +157,14 @@ func TestAuthenticateUser(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
authScenario(t, "When a non-existing grafana user authenticate and ldap returns unexpected error", func(sc *authScenarioContext) {
|
authScenario(t, "When a non-existing grafana user authenticate and ldap returns unexpected error", func(sc *authScenarioContext) {
|
||||||
|
cfg := setting.NewCfg()
|
||||||
|
cfg.LDAPEnabled = true
|
||||||
customErr := errors.New("custom")
|
customErr := errors.New("custom")
|
||||||
mockLoginUsingGrafanaDB(user.ErrUserNotFound, sc)
|
mockLoginUsingGrafanaDB(user.ErrUserNotFound, sc)
|
||||||
mockLoginUsingLDAP(true, customErr, sc)
|
mockLoginUsingLDAP(true, customErr, sc)
|
||||||
|
|
||||||
loginAttemptService := &loginattempttest.MockLoginAttemptService{ExpectedValid: true}
|
loginAttemptService := &loginattempttest.MockLoginAttemptService{ExpectedValid: true}
|
||||||
a := AuthenticatorService{loginAttemptService: loginAttemptService, loginService: &logintest.LoginServiceFake{}}
|
a := AuthenticatorService{loginAttemptService: loginAttemptService, loginService: &logintest.LoginServiceFake{}, cfg: cfg}
|
||||||
err := a.AuthenticateUser(context.Background(), sc.loginUserQuery)
|
err := a.AuthenticateUser(context.Background(), sc.loginUserQuery)
|
||||||
|
|
||||||
require.EqualError(t, err, customErr.Error())
|
require.EqualError(t, err, customErr.Error())
|
||||||
|
|
@ -164,11 +176,13 @@ func TestAuthenticateUser(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
authScenario(t, "When grafana user authenticate with invalid credentials and invalid ldap credentials", func(sc *authScenarioContext) {
|
authScenario(t, "When grafana user authenticate with invalid credentials and invalid ldap credentials", func(sc *authScenarioContext) {
|
||||||
|
cfg := setting.NewCfg()
|
||||||
|
cfg.LDAPEnabled = true
|
||||||
mockLoginUsingGrafanaDB(ErrInvalidCredentials, sc)
|
mockLoginUsingGrafanaDB(ErrInvalidCredentials, sc)
|
||||||
mockLoginUsingLDAP(true, ldap.ErrInvalidCredentials, sc)
|
mockLoginUsingLDAP(true, ldap.ErrInvalidCredentials, sc)
|
||||||
|
|
||||||
loginAttemptService := &loginattempttest.MockLoginAttemptService{ExpectedValid: true}
|
loginAttemptService := &loginattempttest.MockLoginAttemptService{ExpectedValid: true}
|
||||||
a := AuthenticatorService{loginAttemptService: loginAttemptService, loginService: &logintest.LoginServiceFake{}}
|
a := AuthenticatorService{loginAttemptService: loginAttemptService, loginService: &logintest.LoginServiceFake{}, cfg: cfg}
|
||||||
err := a.AuthenticateUser(context.Background(), sc.loginUserQuery)
|
err := a.AuthenticateUser(context.Background(), sc.loginUserQuery)
|
||||||
|
|
||||||
require.EqualError(t, err, ErrInvalidCredentials.Error())
|
require.EqualError(t, err, ErrInvalidCredentials.Error())
|
||||||
|
|
@ -195,7 +209,7 @@ func mockLoginUsingGrafanaDB(err error, sc *authScenarioContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func mockLoginUsingLDAP(enabled bool, err error, sc *authScenarioContext) {
|
func mockLoginUsingLDAP(enabled bool, err error, sc *authScenarioContext) {
|
||||||
loginUsingLDAP = func(ctx context.Context, query *login.LoginUserQuery, _ login.Service) (bool, error) {
|
loginUsingLDAP = func(ctx context.Context, query *login.LoginUserQuery, _ login.Service, _ *setting.Cfg) (bool, error) {
|
||||||
sc.ldapLoginWasCalled = true
|
sc.ldapLoginWasCalled = true
|
||||||
return enabled, err
|
return enabled, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,6 @@ import (
|
||||||
// getLDAPConfig gets LDAP config
|
// getLDAPConfig gets LDAP config
|
||||||
var getLDAPConfig = multildap.GetConfig
|
var getLDAPConfig = multildap.GetConfig
|
||||||
|
|
||||||
// isLDAPEnabled checks if LDAP is enabled
|
|
||||||
var isLDAPEnabled = multildap.IsEnabled
|
|
||||||
|
|
||||||
// newLDAP creates multiple LDAP instance
|
// newLDAP creates multiple LDAP instance
|
||||||
var newLDAP = multildap.New
|
var newLDAP = multildap.New
|
||||||
|
|
||||||
|
|
@ -26,10 +23,9 @@ var ldapLogger = log.New("login.ldap")
|
||||||
|
|
||||||
// loginUsingLDAP logs in user using LDAP. It returns whether LDAP is enabled and optional error and query arg will be
|
// loginUsingLDAP logs in user using LDAP. It returns whether LDAP is enabled and optional error and query arg will be
|
||||||
// populated with the logged in user if successful.
|
// populated with the logged in user if successful.
|
||||||
var loginUsingLDAP = func(ctx context.Context, query *login.LoginUserQuery, loginService login.Service) (bool, error) {
|
var loginUsingLDAP = func(ctx context.Context, query *login.LoginUserQuery,
|
||||||
enabled := isLDAPEnabled()
|
loginService login.Service, cfg *setting.Cfg) (bool, error) {
|
||||||
|
if !cfg.LDAPEnabled {
|
||||||
if !enabled {
|
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -38,7 +34,7 @@ var loginUsingLDAP = func(ctx context.Context, query *login.LoginUserQuery, logi
|
||||||
return true, fmt.Errorf("%v: %w", "Failed to get LDAP config", err)
|
return true, fmt.Errorf("%v: %w", "Failed to get LDAP config", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
externalUser, err := newLDAP(config.Servers).Login(query)
|
externalUser, err := newLDAP(config.Servers, cfg).Login(query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, ldap.ErrCouldNotFindUser) {
|
if errors.Is(err, ldap.ErrCouldNotFindUser) {
|
||||||
// Ignore the error since user might not be present anyway
|
// Ignore the error since user might not be present anyway
|
||||||
|
|
@ -56,7 +52,7 @@ var loginUsingLDAP = func(ctx context.Context, query *login.LoginUserQuery, logi
|
||||||
upsert := &login.UpsertUserCommand{
|
upsert := &login.UpsertUserCommand{
|
||||||
ReqContext: query.ReqContext,
|
ReqContext: query.ReqContext,
|
||||||
ExternalUser: externalUser,
|
ExternalUser: externalUser,
|
||||||
SignupAllowed: setting.LDAPAllowSignup,
|
SignupAllowed: cfg.LDAPAllowSignup,
|
||||||
UserLookupParams: login.UserLookupParams{
|
UserLookupParams: login.UserLookupParams{
|
||||||
Login: &externalUser.Login,
|
Login: &externalUser.Login,
|
||||||
Email: &externalUser.Email,
|
Email: &externalUser.Email,
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,8 @@ var errTest = errors.New("test error")
|
||||||
|
|
||||||
func TestLoginUsingLDAP(t *testing.T) {
|
func TestLoginUsingLDAP(t *testing.T) {
|
||||||
LDAPLoginScenario(t, "When LDAP enabled and no server configured", func(sc *LDAPLoginScenarioContext) {
|
LDAPLoginScenario(t, "When LDAP enabled and no server configured", func(sc *LDAPLoginScenarioContext) {
|
||||||
setting.LDAPEnabled = true
|
cfg := setting.NewCfg()
|
||||||
|
cfg.LDAPEnabled = true
|
||||||
|
|
||||||
sc.withLoginResult(false)
|
sc.withLoginResult(false)
|
||||||
getLDAPConfig = func(*setting.Cfg) (*ldap.Config, error) {
|
getLDAPConfig = func(*setting.Cfg) (*ldap.Config, error) {
|
||||||
|
|
@ -31,7 +32,7 @@ func TestLoginUsingLDAP(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
loginService := &logintest.LoginServiceFake{}
|
loginService := &logintest.LoginServiceFake{}
|
||||||
enabled, err := loginUsingLDAP(context.Background(), sc.loginUserQuery, loginService)
|
enabled, err := loginUsingLDAP(context.Background(), sc.loginUserQuery, loginService, cfg)
|
||||||
require.EqualError(t, err, errTest.Error())
|
require.EqualError(t, err, errTest.Error())
|
||||||
|
|
||||||
assert.True(t, enabled)
|
assert.True(t, enabled)
|
||||||
|
|
@ -39,11 +40,12 @@ func TestLoginUsingLDAP(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
LDAPLoginScenario(t, "When LDAP disabled", func(sc *LDAPLoginScenarioContext) {
|
LDAPLoginScenario(t, "When LDAP disabled", func(sc *LDAPLoginScenarioContext) {
|
||||||
setting.LDAPEnabled = false
|
cfg := setting.NewCfg()
|
||||||
|
cfg.LDAPEnabled = false
|
||||||
|
|
||||||
sc.withLoginResult(false)
|
sc.withLoginResult(false)
|
||||||
loginService := &logintest.LoginServiceFake{}
|
loginService := &logintest.LoginServiceFake{}
|
||||||
enabled, err := loginUsingLDAP(context.Background(), sc.loginUserQuery, loginService)
|
enabled, err := loginUsingLDAP(context.Background(), sc.loginUserQuery, loginService, cfg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.False(t, enabled)
|
assert.False(t, enabled)
|
||||||
|
|
@ -104,7 +106,7 @@ func mockLDAPAuthenticator(valid bool) *mockAuth {
|
||||||
validLogin: valid,
|
validLogin: valid,
|
||||||
}
|
}
|
||||||
|
|
||||||
newLDAP = func(servers []*ldap.ServerConfig) multildap.IMultiLDAP {
|
newLDAP = func(servers []*ldap.ServerConfig, _ *setting.Cfg) multildap.IMultiLDAP {
|
||||||
return mock
|
return mock
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -133,15 +135,6 @@ func LDAPLoginScenario(t *testing.T, desc string, fn LDAPLoginScenarioFunc) {
|
||||||
LDAPAuthenticatorMock: mock,
|
LDAPAuthenticatorMock: mock,
|
||||||
}
|
}
|
||||||
|
|
||||||
origNewLDAP := newLDAP
|
|
||||||
origGetLDAPConfig := getLDAPConfig
|
|
||||||
origLDAPEnabled := setting.LDAPEnabled
|
|
||||||
t.Cleanup(func() {
|
|
||||||
newLDAP = origNewLDAP
|
|
||||||
getLDAPConfig = origGetLDAPConfig
|
|
||||||
setting.LDAPEnabled = origLDAPEnabled
|
|
||||||
})
|
|
||||||
|
|
||||||
getLDAPConfig = func(*setting.Cfg) (*ldap.Config, error) {
|
getLDAPConfig = func(*setting.Cfg) (*ldap.Config, error) {
|
||||||
config := &ldap.Config{
|
config := &ldap.Config{
|
||||||
Servers: []*ldap.ServerConfig{
|
Servers: []*ldap.ServerConfig{
|
||||||
|
|
@ -154,7 +147,7 @@ func LDAPLoginScenario(t *testing.T, desc string, fn LDAPLoginScenarioFunc) {
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
newLDAP = func(server []*ldap.ServerConfig) multildap.IMultiLDAP {
|
newLDAP = func(server []*ldap.ServerConfig, _ *setting.Cfg) multildap.IMultiLDAP {
|
||||||
return mock
|
return mock
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ func TestMiddlewareBasicAuth(t *testing.T) {
|
||||||
|
|
||||||
sc.userService.ExpectedUser = &user.User{Password: encoded, ID: id, Salt: salt}
|
sc.userService.ExpectedUser = &user.User{Password: encoded, ID: id, Salt: salt}
|
||||||
sc.userService.ExpectedSignedInUser = &user.SignedInUser{UserID: id}
|
sc.userService.ExpectedSignedInUser = &user.SignedInUser{UserID: id}
|
||||||
login.ProvideService(sc.mockSQLStore, &logintest.LoginServiceFake{}, nil, sc.userService)
|
login.ProvideService(sc.mockSQLStore, &logintest.LoginServiceFake{}, nil, sc.userService, sc.cfg)
|
||||||
|
|
||||||
authHeader := util.GetBasicAuthHeader("myUser", password)
|
authHeader := util.GetBasicAuthHeader("myUser", password)
|
||||||
sc.fakeReq("GET", "/").withAuthorizationHeader(authHeader).exec()
|
sc.fakeReq("GET", "/").withAuthorizationHeader(authHeader).exec()
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ import (
|
||||||
"github.com/grafana/grafana/pkg/services/contexthandler/authproxy"
|
"github.com/grafana/grafana/pkg/services/contexthandler/authproxy"
|
||||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
|
"github.com/grafana/grafana/pkg/services/ldap/service"
|
||||||
loginsvc "github.com/grafana/grafana/pkg/services/login"
|
loginsvc "github.com/grafana/grafana/pkg/services/login"
|
||||||
"github.com/grafana/grafana/pkg/services/login/loginservice"
|
"github.com/grafana/grafana/pkg/services/login/loginservice"
|
||||||
"github.com/grafana/grafana/pkg/services/login/logintest"
|
"github.com/grafana/grafana/pkg/services/login/logintest"
|
||||||
|
|
@ -950,9 +951,15 @@ func getContextHandler(t *testing.T, cfg *setting.Cfg, mockSQLStore *dbtest.Fake
|
||||||
renderSvc := &fakeRenderService{}
|
renderSvc := &fakeRenderService{}
|
||||||
authJWTSvc := jwt.NewFakeJWTService()
|
authJWTSvc := jwt.NewFakeJWTService()
|
||||||
tracer := tracing.InitializeTracerForTest()
|
tracer := tracing.InitializeTracerForTest()
|
||||||
authProxy := authproxy.ProvideAuthProxy(cfg, remoteCacheSvc, loginService, userService, mockSQLStore)
|
authProxy := authproxy.ProvideAuthProxy(cfg, remoteCacheSvc, loginService,
|
||||||
|
userService, mockSQLStore, &service.LDAPFakeService{ExpectedError: service.ErrUnableToCreateLDAPClient})
|
||||||
authenticator := &logintest.AuthenticatorFake{ExpectedUser: &user.User{}}
|
authenticator := &logintest.AuthenticatorFake{ExpectedUser: &user.User{}}
|
||||||
return contexthandler.ProvideService(cfg, userAuthTokenSvc, authJWTSvc, remoteCacheSvc, renderSvc, mockSQLStore, tracer, authProxy, loginService, apiKeyService, authenticator, userService, orgService, oauthTokenService, featuremgmt.WithFeatures(featuremgmt.FlagAccessTokenExpirationCheck), &authntest.FakeService{})
|
return contexthandler.ProvideService(cfg, userAuthTokenSvc, authJWTSvc,
|
||||||
|
remoteCacheSvc, renderSvc, mockSQLStore, tracer, authProxy,
|
||||||
|
loginService, apiKeyService, authenticator, userService, orgService,
|
||||||
|
oauthTokenService,
|
||||||
|
featuremgmt.WithFeatures(featuremgmt.FlagAccessTokenExpirationCheck),
|
||||||
|
&authntest.FakeService{})
|
||||||
}
|
}
|
||||||
|
|
||||||
type fakeRenderService struct {
|
type fakeRenderService struct {
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,7 @@ import (
|
||||||
"github.com/grafana/grafana/pkg/services/guardian"
|
"github.com/grafana/grafana/pkg/services/guardian"
|
||||||
"github.com/grafana/grafana/pkg/services/hooks"
|
"github.com/grafana/grafana/pkg/services/hooks"
|
||||||
ldapapi "github.com/grafana/grafana/pkg/services/ldap/api"
|
ldapapi "github.com/grafana/grafana/pkg/services/ldap/api"
|
||||||
|
ldapservice "github.com/grafana/grafana/pkg/services/ldap/service"
|
||||||
"github.com/grafana/grafana/pkg/services/libraryelements"
|
"github.com/grafana/grafana/pkg/services/libraryelements"
|
||||||
"github.com/grafana/grafana/pkg/services/librarypanels"
|
"github.com/grafana/grafana/pkg/services/librarypanels"
|
||||||
"github.com/grafana/grafana/pkg/services/live"
|
"github.com/grafana/grafana/pkg/services/live"
|
||||||
|
|
@ -234,6 +235,8 @@ var wireBasicSet = wire.NewSet(
|
||||||
live.ProvideService,
|
live.ProvideService,
|
||||||
pushhttp.ProvideService,
|
pushhttp.ProvideService,
|
||||||
contexthandler.ProvideService,
|
contexthandler.ProvideService,
|
||||||
|
ldapservice.ProvideService,
|
||||||
|
wire.Bind(new(ldapservice.LDAP), new(*ldapservice.LDAPImpl)),
|
||||||
jwt.ProvideService,
|
jwt.ProvideService,
|
||||||
wire.Bind(new(jwt.JWTService), new(*jwt.AuthService)),
|
wire.Bind(new(jwt.JWTService), new(*jwt.AuthService)),
|
||||||
ngstore.ProvideDBStore,
|
ngstore.ProvideDBStore,
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,13 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/remotecache"
|
|
||||||
"github.com/hashicorp/go-multierror"
|
"github.com/hashicorp/go-multierror"
|
||||||
"go.opentelemetry.io/otel/attribute"
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/kvstore"
|
"github.com/grafana/grafana/pkg/infra/kvstore"
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/infra/network"
|
"github.com/grafana/grafana/pkg/infra/network"
|
||||||
|
"github.com/grafana/grafana/pkg/infra/remotecache"
|
||||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||||
"github.com/grafana/grafana/pkg/infra/usagestats"
|
"github.com/grafana/grafana/pkg/infra/usagestats"
|
||||||
"github.com/grafana/grafana/pkg/login/social"
|
"github.com/grafana/grafana/pkg/login/social"
|
||||||
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"github.com/grafana/grafana/pkg/services/authn/authnimpl/sync"
|
"github.com/grafana/grafana/pkg/services/authn/authnimpl/sync"
|
||||||
"github.com/grafana/grafana/pkg/services/authn/clients"
|
"github.com/grafana/grafana/pkg/services/authn/clients"
|
||||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
|
"github.com/grafana/grafana/pkg/services/ldap/service"
|
||||||
"github.com/grafana/grafana/pkg/services/login"
|
"github.com/grafana/grafana/pkg/services/login"
|
||||||
"github.com/grafana/grafana/pkg/services/loginattempt"
|
"github.com/grafana/grafana/pkg/services/loginattempt"
|
||||||
"github.com/grafana/grafana/pkg/services/oauthtoken"
|
"github.com/grafana/grafana/pkg/services/oauthtoken"
|
||||||
|
|
@ -59,6 +60,7 @@ func ProvideService(
|
||||||
authInfoService login.AuthInfoService, renderService rendering.Service,
|
authInfoService login.AuthInfoService, renderService rendering.Service,
|
||||||
features *featuremgmt.FeatureManager, oauthTokenService oauthtoken.OAuthTokenService,
|
features *featuremgmt.FeatureManager, oauthTokenService oauthtoken.OAuthTokenService,
|
||||||
socialService social.Service, cache *remotecache.RemoteCache,
|
socialService social.Service, cache *remotecache.RemoteCache,
|
||||||
|
ldapService service.LDAP,
|
||||||
) *Service {
|
) *Service {
|
||||||
s := &Service{
|
s := &Service{
|
||||||
log: log.New("authn.service"),
|
log: log.New("authn.service"),
|
||||||
|
|
@ -87,7 +89,7 @@ func ProvideService(
|
||||||
var proxyClients []authn.ProxyClient
|
var proxyClients []authn.ProxyClient
|
||||||
var passwordClients []authn.PasswordClient
|
var passwordClients []authn.PasswordClient
|
||||||
if s.cfg.LDAPEnabled {
|
if s.cfg.LDAPEnabled {
|
||||||
ldap := clients.ProvideLDAP(cfg)
|
ldap := clients.ProvideLDAP(cfg, ldapService)
|
||||||
proxyClients = append(proxyClients, ldap)
|
proxyClients = append(proxyClients, ldap)
|
||||||
passwordClients = append(passwordClients, ldap)
|
passwordClients = append(passwordClients, ldap)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,13 @@ import (
|
||||||
var _ authn.ProxyClient = new(LDAP)
|
var _ authn.ProxyClient = new(LDAP)
|
||||||
var _ authn.PasswordClient = new(LDAP)
|
var _ authn.PasswordClient = new(LDAP)
|
||||||
|
|
||||||
func ProvideLDAP(cfg *setting.Cfg) *LDAP {
|
type ldapService interface {
|
||||||
return &LDAP{cfg, &ldapServiceImpl{cfg}}
|
Login(query *login.LoginUserQuery) (*login.ExternalUserInfo, error)
|
||||||
|
User(username string) (*login.ExternalUserInfo, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ProvideLDAP(cfg *setting.Cfg, ldapService ldapService) *LDAP {
|
||||||
|
return &LDAP{cfg, ldapService}
|
||||||
}
|
}
|
||||||
|
|
||||||
type LDAP struct {
|
type LDAP struct {
|
||||||
|
|
@ -60,35 +65,6 @@ func (c *LDAP) AuthenticatePassword(ctx context.Context, r *authn.Request, usern
|
||||||
return identityFromLDAPInfo(r.OrgID, info, c.cfg.LDAPAllowSignup), nil
|
return identityFromLDAPInfo(r.OrgID, info, c.cfg.LDAPAllowSignup), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type ldapService interface {
|
|
||||||
Login(query *login.LoginUserQuery) (*login.ExternalUserInfo, error)
|
|
||||||
User(username string) (*login.ExternalUserInfo, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: remove the implementation if we convert ldap to an actual service
|
|
||||||
type ldapServiceImpl struct {
|
|
||||||
cfg *setting.Cfg
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ldapServiceImpl) Login(query *login.LoginUserQuery) (*login.ExternalUserInfo, error) {
|
|
||||||
cfg, err := multildap.GetConfig(s.cfg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return multildap.New(cfg.Servers).Login(query)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ldapServiceImpl) User(username string) (*login.ExternalUserInfo, error) {
|
|
||||||
cfg, err := multildap.GetConfig(s.cfg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
user, _, err := multildap.New(cfg.Servers).User(username)
|
|
||||||
return user, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func identityFromLDAPInfo(orgID int64, info *login.ExternalUserInfo, allowSignup bool) *authn.Identity {
|
func identityFromLDAPInfo(orgID int64, info *login.ExternalUserInfo, allowSignup bool) *authn.Identity {
|
||||||
return &authn.Identity{
|
return &authn.Identity{
|
||||||
OrgID: orgID,
|
OrgID: orgID,
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/grafana/grafana/pkg/services/authn"
|
"github.com/grafana/grafana/pkg/services/authn"
|
||||||
"github.com/grafana/grafana/pkg/services/ldap"
|
"github.com/grafana/grafana/pkg/services/ldap"
|
||||||
"github.com/grafana/grafana/pkg/services/ldap/multildap"
|
"github.com/grafana/grafana/pkg/services/ldap/multildap"
|
||||||
|
"github.com/grafana/grafana/pkg/services/ldap/service"
|
||||||
"github.com/grafana/grafana/pkg/services/login"
|
"github.com/grafana/grafana/pkg/services/login"
|
||||||
"github.com/grafana/grafana/pkg/services/org"
|
"github.com/grafana/grafana/pkg/services/org"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
|
|
@ -68,7 +69,7 @@ func TestLDAP_AuthenticateProxy(t *testing.T) {
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.desc, func(t *testing.T) {
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
c := &LDAP{cfg: setting.NewCfg(), service: fakeLDAPService{ExpectedInfo: tt.expectedLDAPInfo, ExpectedErr: tt.expectedLDAPErr}}
|
c := &LDAP{cfg: setting.NewCfg(), service: &service.LDAPFakeService{ExpectedUser: tt.expectedLDAPInfo, ExpectedError: tt.expectedLDAPErr}}
|
||||||
identity, err := c.AuthenticateProxy(context.Background(), &authn.Request{OrgID: 1}, tt.username, nil)
|
identity, err := c.AuthenticateProxy(context.Background(), &authn.Request{OrgID: 1}, tt.username, nil)
|
||||||
assert.ErrorIs(t, err, tt.expectedErr)
|
assert.ErrorIs(t, err, tt.expectedErr)
|
||||||
assert.EqualValues(t, tt.expectedIdentity, identity)
|
assert.EqualValues(t, tt.expectedIdentity, identity)
|
||||||
|
|
@ -140,7 +141,7 @@ func TestLDAP_AuthenticatePassword(t *testing.T) {
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.desc, func(t *testing.T) {
|
t.Run(tt.desc, func(t *testing.T) {
|
||||||
c := &LDAP{cfg: setting.NewCfg(), service: fakeLDAPService{ExpectedInfo: tt.expectedLDAPInfo, ExpectedErr: tt.expectedLDAPErr}}
|
c := &LDAP{cfg: setting.NewCfg(), service: &service.LDAPFakeService{ExpectedUser: tt.expectedLDAPInfo, ExpectedError: tt.expectedLDAPErr}}
|
||||||
|
|
||||||
identity, err := c.AuthenticatePassword(context.Background(), &authn.Request{OrgID: 1}, tt.username, tt.password)
|
identity, err := c.AuthenticatePassword(context.Background(), &authn.Request{OrgID: 1}, tt.username, tt.password)
|
||||||
assert.ErrorIs(t, err, tt.expectedErr)
|
assert.ErrorIs(t, err, tt.expectedErr)
|
||||||
|
|
@ -152,18 +153,3 @@ func TestLDAP_AuthenticatePassword(t *testing.T) {
|
||||||
func strPtr(s string) *string {
|
func strPtr(s string) *string {
|
||||||
return &s
|
return &s
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ ldapService = new(fakeLDAPService)
|
|
||||||
|
|
||||||
type fakeLDAPService struct {
|
|
||||||
ExpectedErr error
|
|
||||||
ExpectedInfo *login.ExternalUserInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f fakeLDAPService) Login(query *login.LoginUserQuery) (*login.ExternalUserInfo, error) {
|
|
||||||
return f.ExpectedInfo, f.ExpectedErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f fakeLDAPService) User(username string) (*login.ExternalUserInfo, error) {
|
|
||||||
return f.ExpectedInfo, f.ExpectedErr
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ import (
|
||||||
"github.com/grafana/grafana/pkg/services/contexthandler/authproxy"
|
"github.com/grafana/grafana/pkg/services/contexthandler/authproxy"
|
||||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
|
"github.com/grafana/grafana/pkg/services/ldap/service"
|
||||||
"github.com/grafana/grafana/pkg/services/login"
|
"github.com/grafana/grafana/pkg/services/login"
|
||||||
"github.com/grafana/grafana/pkg/services/login/loginservice"
|
"github.com/grafana/grafana/pkg/services/login/loginservice"
|
||||||
"github.com/grafana/grafana/pkg/services/org/orgtest"
|
"github.com/grafana/grafana/pkg/services/org/orgtest"
|
||||||
|
|
@ -110,7 +111,7 @@ func getContextHandler(t *testing.T) *ContextHandler {
|
||||||
}
|
}
|
||||||
orgService := orgtest.NewOrgServiceFake()
|
orgService := orgtest.NewOrgServiceFake()
|
||||||
|
|
||||||
authProxy := authproxy.ProvideAuthProxy(cfg, remoteCacheSvc, loginService, &userService, nil)
|
authProxy := authproxy.ProvideAuthProxy(cfg, remoteCacheSvc, loginService, &userService, nil, service.NewLDAPFakeService())
|
||||||
authenticator := &fakeAuthenticator{}
|
authenticator := &fakeAuthenticator{}
|
||||||
|
|
||||||
return ProvideService(cfg, userAuthTokenSvc, authJWTSvc, remoteCacheSvc,
|
return ProvideService(cfg, userAuthTokenSvc, authJWTSvc, remoteCacheSvc,
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import (
|
||||||
"github.com/grafana/grafana/pkg/infra/remotecache"
|
"github.com/grafana/grafana/pkg/infra/remotecache"
|
||||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||||
"github.com/grafana/grafana/pkg/services/ldap"
|
"github.com/grafana/grafana/pkg/services/ldap"
|
||||||
"github.com/grafana/grafana/pkg/services/ldap/multildap"
|
"github.com/grafana/grafana/pkg/services/ldap/service"
|
||||||
"github.com/grafana/grafana/pkg/services/login"
|
"github.com/grafana/grafana/pkg/services/login"
|
||||||
"github.com/grafana/grafana/pkg/services/org"
|
"github.com/grafana/grafana/pkg/services/org"
|
||||||
"github.com/grafana/grafana/pkg/services/user"
|
"github.com/grafana/grafana/pkg/services/user"
|
||||||
|
|
@ -33,21 +33,6 @@ const (
|
||||||
CachePrefix = "auth-proxy-sync-ttl:%s"
|
CachePrefix = "auth-proxy-sync-ttl:%s"
|
||||||
)
|
)
|
||||||
|
|
||||||
// getLDAPConfig gets LDAP config
|
|
||||||
var getLDAPConfig = ldap.GetConfig
|
|
||||||
|
|
||||||
// isLDAPEnabled checks if LDAP is enabled
|
|
||||||
var isLDAPEnabled = func(cfg *setting.Cfg) bool {
|
|
||||||
if cfg != nil {
|
|
||||||
return cfg.LDAPEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
return setting.LDAPEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
// newLDAP creates multiple LDAP instance
|
|
||||||
var newLDAP = multildap.New
|
|
||||||
|
|
||||||
// supportedHeaders states the supported headers configuration fields
|
// supportedHeaders states the supported headers configuration fields
|
||||||
var supportedHeaderFields = []string{"Name", "Email", "Login", "Groups", "Role"}
|
var supportedHeaderFields = []string{"Name", "Email", "Login", "Groups", "Role"}
|
||||||
|
|
||||||
|
|
@ -58,11 +43,14 @@ type AuthProxy struct {
|
||||||
loginService login.Service
|
loginService login.Service
|
||||||
sqlStore db.DB
|
sqlStore db.DB
|
||||||
userService user.Service
|
userService user.Service
|
||||||
|
ldapService service.LDAP
|
||||||
|
|
||||||
logger log.Logger
|
logger log.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func ProvideAuthProxy(cfg *setting.Cfg, remoteCache *remotecache.RemoteCache, loginService login.Service, userService user.Service, sqlStore db.DB) *AuthProxy {
|
func ProvideAuthProxy(cfg *setting.Cfg, remoteCache *remotecache.RemoteCache,
|
||||||
|
loginService login.Service, userService user.Service,
|
||||||
|
sqlStore db.DB, ldapService service.LDAP) *AuthProxy {
|
||||||
return &AuthProxy{
|
return &AuthProxy{
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
remoteCache: remoteCache,
|
remoteCache: remoteCache,
|
||||||
|
|
@ -70,6 +58,7 @@ func ProvideAuthProxy(cfg *setting.Cfg, remoteCache *remotecache.RemoteCache, lo
|
||||||
sqlStore: sqlStore,
|
sqlStore: sqlStore,
|
||||||
userService: userService,
|
userService: userService,
|
||||||
logger: log.New("auth.proxy"),
|
logger: log.New("auth.proxy"),
|
||||||
|
ldapService: ldapService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -175,7 +164,7 @@ func (auth *AuthProxy) Login(reqCtx *contextmodel.ReqContext, ignoreCache bool)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if isLDAPEnabled(auth.cfg) {
|
if auth.cfg.LDAPEnabled {
|
||||||
id, err := auth.LoginViaLDAP(reqCtx)
|
id, err := auth.LoginViaLDAP(reqCtx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, ldap.ErrInvalidCredentials) {
|
if errors.Is(err, ldap.ErrInvalidCredentials) {
|
||||||
|
|
@ -234,14 +223,9 @@ func (auth *AuthProxy) RemoveUserFromCache(reqCtx *contextmodel.ReqContext) erro
|
||||||
|
|
||||||
// LoginViaLDAP logs in user via LDAP request
|
// LoginViaLDAP logs in user via LDAP request
|
||||||
func (auth *AuthProxy) LoginViaLDAP(reqCtx *contextmodel.ReqContext) (int64, error) {
|
func (auth *AuthProxy) LoginViaLDAP(reqCtx *contextmodel.ReqContext) (int64, error) {
|
||||||
config, err := getLDAPConfig(auth.cfg)
|
|
||||||
if err != nil {
|
|
||||||
return 0, newError("failed to get LDAP config", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
header := auth.getDecodedHeader(reqCtx, auth.cfg.AuthProxyHeaderName)
|
header := auth.getDecodedHeader(reqCtx, auth.cfg.AuthProxyHeaderName)
|
||||||
mldap := newLDAP(config.Servers)
|
|
||||||
extUser, _, err := mldap.User(header)
|
extUser, err := auth.ldapService.User(header)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package authproxy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
@ -13,8 +12,8 @@ import (
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/remotecache"
|
"github.com/grafana/grafana/pkg/infra/remotecache"
|
||||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||||
"github.com/grafana/grafana/pkg/services/ldap"
|
"github.com/grafana/grafana/pkg/services/ldap/service"
|
||||||
"github.com/grafana/grafana/pkg/services/ldap/multildap"
|
"github.com/grafana/grafana/pkg/services/login"
|
||||||
"github.com/grafana/grafana/pkg/services/login/loginservice"
|
"github.com/grafana/grafana/pkg/services/login/loginservice"
|
||||||
"github.com/grafana/grafana/pkg/services/user"
|
"github.com/grafana/grafana/pkg/services/user"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
|
|
@ -49,7 +48,7 @@ func prepareMiddleware(t *testing.T, remoteCache *remotecache.RemoteCache, confi
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return ProvideAuthProxy(cfg, remoteCache, loginService, nil, nil), ctx
|
return ProvideAuthProxy(cfg, remoteCache, loginService, nil, nil, service.NewLDAPFakeService()), ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMiddlewareContext(t *testing.T) {
|
func TestMiddlewareContext(t *testing.T) {
|
||||||
|
|
@ -105,85 +104,41 @@ func TestMiddlewareContext(t *testing.T) {
|
||||||
|
|
||||||
func TestMiddlewareContext_ldap(t *testing.T) {
|
func TestMiddlewareContext_ldap(t *testing.T) {
|
||||||
t.Run("Logs in via LDAP", func(t *testing.T) {
|
t.Run("Logs in via LDAP", func(t *testing.T) {
|
||||||
origIsLDAPEnabled := isLDAPEnabled
|
|
||||||
origGetLDAPConfig := getLDAPConfig
|
|
||||||
origNewLDAP := newLDAP
|
|
||||||
t.Cleanup(func() {
|
|
||||||
newLDAP = origNewLDAP
|
|
||||||
isLDAPEnabled = origIsLDAPEnabled
|
|
||||||
getLDAPConfig = origGetLDAPConfig
|
|
||||||
})
|
|
||||||
|
|
||||||
isLDAPEnabled = func(*setting.Cfg) bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
stub := &multildap.MultiLDAPmock{
|
|
||||||
ID: id,
|
|
||||||
}
|
|
||||||
|
|
||||||
getLDAPConfig = func(*setting.Cfg) (*ldap.Config, error) {
|
|
||||||
config := &ldap.Config{
|
|
||||||
Servers: []*ldap.ServerConfig{
|
|
||||||
{
|
|
||||||
SearchBaseDNs: []string{"BaseDNHere"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return config, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
newLDAP = func(servers []*ldap.ServerConfig) multildap.IMultiLDAP {
|
|
||||||
return stub
|
|
||||||
}
|
|
||||||
|
|
||||||
cache := remotecache.NewFakeStore(t)
|
cache := remotecache.NewFakeStore(t)
|
||||||
|
|
||||||
auth, reqCtx := prepareMiddleware(t, cache, nil)
|
auth, reqCtx := prepareMiddleware(t, cache, nil)
|
||||||
|
auth.cfg.LDAPEnabled = true
|
||||||
|
ldapFake := &service.LDAPFakeService{
|
||||||
|
ExpectedUser: &login.ExternalUserInfo{UserId: id},
|
||||||
|
}
|
||||||
|
|
||||||
|
auth.ldapService = ldapFake
|
||||||
|
|
||||||
gotID, err := auth.Login(reqCtx, false)
|
gotID, err := auth.Login(reqCtx, false)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, id, gotID)
|
assert.Equal(t, id, gotID)
|
||||||
assert.True(t, stub.UserCalled)
|
assert.True(t, ldapFake.UserCalled)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Gets nice error if LDAP is enabled, but not configured", func(t *testing.T) {
|
t.Run("Gets nice error if LDAP is enabled, but not configured", func(t *testing.T) {
|
||||||
const id int64 = 42
|
const id int64 = 42
|
||||||
origIsLDAPEnabled := isLDAPEnabled
|
|
||||||
origNewLDAP := newLDAP
|
|
||||||
origGetLDAPConfig := getLDAPConfig
|
|
||||||
t.Cleanup(func() {
|
|
||||||
isLDAPEnabled = origIsLDAPEnabled
|
|
||||||
newLDAP = origNewLDAP
|
|
||||||
getLDAPConfig = origGetLDAPConfig
|
|
||||||
})
|
|
||||||
|
|
||||||
isLDAPEnabled = func(*setting.Cfg) bool {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
getLDAPConfig = func(*setting.Cfg) (*ldap.Config, error) {
|
|
||||||
return nil, errors.New("something went wrong")
|
|
||||||
}
|
|
||||||
|
|
||||||
cache := remotecache.NewFakeStore(t)
|
cache := remotecache.NewFakeStore(t)
|
||||||
|
|
||||||
auth, reqCtx := prepareMiddleware(t, cache, nil)
|
auth, reqCtx := prepareMiddleware(t, cache, nil)
|
||||||
|
auth.cfg.LDAPEnabled = true
|
||||||
stub := &multildap.MultiLDAPmock{
|
ldapFake := &service.LDAPFakeService{
|
||||||
ID: id,
|
ExpectedUser: nil,
|
||||||
|
ExpectedError: service.ErrUnableToCreateLDAPClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
newLDAP = func(servers []*ldap.ServerConfig) multildap.IMultiLDAP {
|
auth.ldapService = ldapFake
|
||||||
return stub
|
|
||||||
}
|
|
||||||
|
|
||||||
gotID, err := auth.Login(reqCtx, false)
|
gotID, err := auth.Login(reqCtx, false)
|
||||||
require.EqualError(t, err, "failed to get the user")
|
require.EqualError(t, err, "failed to get the user")
|
||||||
|
|
||||||
assert.NotEqual(t, id, gotID)
|
assert.NotEqual(t, id, gotID)
|
||||||
assert.False(t, stub.LoginCalled)
|
assert.True(t, ldapFake.UserCalled)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/grafana/grafana/pkg/services/ldap"
|
||||||
|
"github.com/grafana/grafana/pkg/services/org"
|
||||||
|
)
|
||||||
|
|
||||||
|
// swagger:parameters getUserFromLDAP
|
||||||
|
type GetLDAPUserParams struct {
|
||||||
|
// in:path
|
||||||
|
// required:true
|
||||||
|
UserName string `json:"user_name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// swagger:parameters postSyncUserWithLDAP
|
||||||
|
type SyncLDAPUserParams struct {
|
||||||
|
// in:path
|
||||||
|
// required:true
|
||||||
|
UserID int64 `json:"user_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LDAPAttribute is a serializer for user attributes mapped from LDAP. Is meant to display both the serialized value and the LDAP key we received it from.
|
||||||
|
type LDAPAttribute struct {
|
||||||
|
ConfigAttributeValue string `json:"cfgAttrValue"`
|
||||||
|
LDAPAttributeValue string `json:"ldapValue"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoleDTO is a serializer for mapped roles from LDAP
|
||||||
|
type LDAPRoleDTO struct {
|
||||||
|
OrgId int64 `json:"orgId"`
|
||||||
|
OrgName string `json:"orgName"`
|
||||||
|
OrgRole org.RoleType `json:"orgRole"`
|
||||||
|
GroupDN string `json:"groupDN"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LDAPUserDTO is a serializer for users mapped from LDAP
|
||||||
|
type LDAPUserDTO struct {
|
||||||
|
Name *LDAPAttribute `json:"name"`
|
||||||
|
Surname *LDAPAttribute `json:"surname"`
|
||||||
|
Email *LDAPAttribute `json:"email"`
|
||||||
|
Username *LDAPAttribute `json:"login"`
|
||||||
|
IsGrafanaAdmin *bool `json:"isGrafanaAdmin"`
|
||||||
|
IsDisabled bool `json:"isDisabled"`
|
||||||
|
OrgRoles []LDAPRoleDTO `json:"roles"`
|
||||||
|
Teams []ldap.TeamOrgGroupDTO `json:"teams"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LDAPServerDTO is a serializer for LDAP server statuses
|
||||||
|
type LDAPServerDTO struct {
|
||||||
|
Host string `json:"host"`
|
||||||
|
Port int `json:"port"`
|
||||||
|
Available bool `json:"available"`
|
||||||
|
Error string `json:"error"`
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type OrganizationNotFoundError struct {
|
||||||
|
OrgID int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *OrganizationNotFoundError) Error() string {
|
||||||
|
return fmt.Sprintf("unable to find organization with ID '%d'", e.OrgID)
|
||||||
|
}
|
||||||
|
|
@ -17,6 +17,7 @@ import (
|
||||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||||
"github.com/grafana/grafana/pkg/services/ldap"
|
"github.com/grafana/grafana/pkg/services/ldap"
|
||||||
"github.com/grafana/grafana/pkg/services/ldap/multildap"
|
"github.com/grafana/grafana/pkg/services/ldap/multildap"
|
||||||
|
"github.com/grafana/grafana/pkg/services/ldap/service"
|
||||||
"github.com/grafana/grafana/pkg/services/login"
|
"github.com/grafana/grafana/pkg/services/login"
|
||||||
"github.com/grafana/grafana/pkg/services/org"
|
"github.com/grafana/grafana/pkg/services/org"
|
||||||
"github.com/grafana/grafana/pkg/services/supportbundles"
|
"github.com/grafana/grafana/pkg/services/supportbundles"
|
||||||
|
|
@ -26,15 +27,6 @@ import (
|
||||||
"github.com/grafana/grafana/pkg/web"
|
"github.com/grafana/grafana/pkg/web"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
getLDAPConfig = multildap.GetConfig
|
|
||||||
newLDAP = multildap.New
|
|
||||||
|
|
||||||
errOrganizationNotFound = func(orgId int64) error {
|
|
||||||
return fmt.Errorf("unable to find organization with ID '%d'", orgId)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
cfg *setting.Cfg
|
cfg *setting.Cfg
|
||||||
userService user.Service
|
userService user.Service
|
||||||
|
|
@ -44,11 +36,12 @@ type Service struct {
|
||||||
orgService org.Service
|
orgService org.Service
|
||||||
sessionService auth.UserTokenService
|
sessionService auth.UserTokenService
|
||||||
log log.Logger
|
log log.Logger
|
||||||
|
ldapService service.LDAP
|
||||||
}
|
}
|
||||||
|
|
||||||
func ProvideService(cfg *setting.Cfg, router routing.RouteRegister, accessControl ac.AccessControl,
|
func ProvideService(cfg *setting.Cfg, router routing.RouteRegister, accessControl ac.AccessControl,
|
||||||
userService user.Service, authInfoService login.AuthInfoService, ldapGroupsService ldap.Groups,
|
userService user.Service, authInfoService login.AuthInfoService, ldapGroupsService ldap.Groups,
|
||||||
loginService login.Service, orgService org.Service,
|
loginService login.Service, orgService org.Service, ldapService service.LDAP,
|
||||||
sessionService auth.UserTokenService, bundleRegistry supportbundles.Service) *Service {
|
sessionService auth.UserTokenService, bundleRegistry supportbundles.Service) *Service {
|
||||||
s := &Service{
|
s := &Service{
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
|
|
@ -58,6 +51,7 @@ func ProvideService(cfg *setting.Cfg, router routing.RouteRegister, accessContro
|
||||||
loginService: loginService,
|
loginService: loginService,
|
||||||
orgService: orgService,
|
orgService: orgService,
|
||||||
sessionService: sessionService,
|
sessionService: sessionService,
|
||||||
|
ldapService: ldapService,
|
||||||
log: log.New("ldap.api"),
|
log: log.New("ldap.api"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,78 +79,6 @@ func ProvideService(cfg *setting.Cfg, router routing.RouteRegister, accessContro
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// LDAPAttribute is a serializer for user attributes mapped from LDAP. Is meant to display both the serialized value and the LDAP key we received it from.
|
|
||||||
type LDAPAttribute struct {
|
|
||||||
ConfigAttributeValue string `json:"cfgAttrValue"`
|
|
||||||
LDAPAttributeValue string `json:"ldapValue"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// RoleDTO is a serializer for mapped roles from LDAP
|
|
||||||
type LDAPRoleDTO struct {
|
|
||||||
OrgId int64 `json:"orgId"`
|
|
||||||
OrgName string `json:"orgName"`
|
|
||||||
OrgRole org.RoleType `json:"orgRole"`
|
|
||||||
GroupDN string `json:"groupDN"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// LDAPUserDTO is a serializer for users mapped from LDAP
|
|
||||||
type LDAPUserDTO struct {
|
|
||||||
Name *LDAPAttribute `json:"name"`
|
|
||||||
Surname *LDAPAttribute `json:"surname"`
|
|
||||||
Email *LDAPAttribute `json:"email"`
|
|
||||||
Username *LDAPAttribute `json:"login"`
|
|
||||||
IsGrafanaAdmin *bool `json:"isGrafanaAdmin"`
|
|
||||||
IsDisabled bool `json:"isDisabled"`
|
|
||||||
OrgRoles []LDAPRoleDTO `json:"roles"`
|
|
||||||
Teams []ldap.TeamOrgGroupDTO `json:"teams"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// LDAPServerDTO is a serializer for LDAP server statuses
|
|
||||||
type LDAPServerDTO struct {
|
|
||||||
Host string `json:"host"`
|
|
||||||
Port int `json:"port"`
|
|
||||||
Available bool `json:"available"`
|
|
||||||
Error string `json:"error"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// FetchOrgs fetches the organization(s) information by executing a single query to the database. Then, populating the DTO with the information retrieved.
|
|
||||||
func (user *LDAPUserDTO) FetchOrgs(ctx context.Context, orga org.Service) error {
|
|
||||||
orgIds := []int64{}
|
|
||||||
|
|
||||||
for _, or := range user.OrgRoles {
|
|
||||||
orgIds = append(orgIds, or.OrgId)
|
|
||||||
}
|
|
||||||
|
|
||||||
q := &org.SearchOrgsQuery{}
|
|
||||||
q.IDs = orgIds
|
|
||||||
|
|
||||||
result, err := orga.Search(ctx, q)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
orgNamesById := map[int64]string{}
|
|
||||||
for _, org := range result {
|
|
||||||
orgNamesById[org.ID] = org.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, orgDTO := range user.OrgRoles {
|
|
||||||
if orgDTO.OrgId < 1 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
orgName := orgNamesById[orgDTO.OrgId]
|
|
||||||
|
|
||||||
if orgName != "" {
|
|
||||||
user.OrgRoles[i].OrgName = orgName
|
|
||||||
} else {
|
|
||||||
return errOrganizationNotFound(orgDTO.OrgId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// swagger:route POST /admin/ldap/reload admin_ldap reloadLDAPCfg
|
// swagger:route POST /admin/ldap/reload admin_ldap reloadLDAPCfg
|
||||||
//
|
//
|
||||||
// Reloads the LDAP configuration.
|
// Reloads the LDAP configuration.
|
||||||
|
|
@ -176,10 +98,10 @@ func (s *Service) ReloadLDAPCfg(c *contextmodel.ReqContext) response.Response {
|
||||||
return response.Error(http.StatusBadRequest, "LDAP is not enabled", nil)
|
return response.Error(http.StatusBadRequest, "LDAP is not enabled", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := ldap.ReloadConfig(s.cfg.LDAPConfigFilePath)
|
if err := s.ldapService.ReloadConfig(); err != nil {
|
||||||
if err != nil {
|
|
||||||
return response.Error(http.StatusInternalServerError, "Failed to reload LDAP config", err)
|
return response.Error(http.StatusInternalServerError, "Failed to reload LDAP config", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.Success("LDAP config reloaded")
|
return response.Success("LDAP config reloaded")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -202,18 +124,12 @@ func (s *Service) GetLDAPStatus(c *contextmodel.ReqContext) response.Response {
|
||||||
return response.Error(http.StatusBadRequest, "LDAP is not enabled", nil)
|
return response.Error(http.StatusBadRequest, "LDAP is not enabled", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
ldapConfig, err := getLDAPConfig(s.cfg)
|
ldapClient := s.ldapService.Client()
|
||||||
if err != nil {
|
if ldapClient == nil {
|
||||||
return response.Error(http.StatusBadRequest, "Failed to obtain the LDAP configuration. Please verify the configuration and try again", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ldap := newLDAP(ldapConfig.Servers)
|
|
||||||
|
|
||||||
if ldap == nil {
|
|
||||||
return response.Error(http.StatusInternalServerError, "Failed to find the LDAP server", nil)
|
return response.Error(http.StatusInternalServerError, "Failed to find the LDAP server", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
statuses, err := ldap.Ping()
|
statuses, err := ldapClient.Ping()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response.Error(http.StatusBadRequest, "Failed to connect to the LDAP server(s)", err)
|
return response.Error(http.StatusBadRequest, "Failed to connect to the LDAP server(s)", err)
|
||||||
}
|
}
|
||||||
|
|
@ -255,9 +171,9 @@ func (s *Service) PostSyncUserWithLDAP(c *contextmodel.ReqContext) response.Resp
|
||||||
return response.Error(http.StatusBadRequest, "LDAP is not enabled", nil)
|
return response.Error(http.StatusBadRequest, "LDAP is not enabled", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
ldapConfig, err := getLDAPConfig(s.cfg)
|
ldapClient := s.ldapService.Client()
|
||||||
if err != nil {
|
if ldapClient == nil {
|
||||||
return response.Error(http.StatusBadRequest, "Failed to obtain the LDAP configuration. Please verify the configuration and try again", err)
|
return response.Error(http.StatusInternalServerError, "Failed to find the LDAP server", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
userId, err := strconv.ParseInt(web.Params(c.Req)[":id"], 10, 64)
|
userId, err := strconv.ParseInt(web.Params(c.Req)[":id"], 10, 64)
|
||||||
|
|
@ -270,10 +186,10 @@ func (s *Service) PostSyncUserWithLDAP(c *contextmodel.ReqContext) response.Resp
|
||||||
usr, err := s.userService.GetByID(c.Req.Context(), &query)
|
usr, err := s.userService.GetByID(c.Req.Context(), &query)
|
||||||
if err != nil { // validate the userId exists
|
if err != nil { // validate the userId exists
|
||||||
if errors.Is(err, user.ErrUserNotFound) {
|
if errors.Is(err, user.ErrUserNotFound) {
|
||||||
return response.Error(404, user.ErrUserNotFound.Error(), nil)
|
return response.Error(http.StatusNotFound, user.ErrUserNotFound.Error(), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.Error(500, "Failed to get user", err)
|
return response.Error(http.StatusInternalServerError, "Failed to get user", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
authModuleQuery := &login.GetAuthInfoQuery{UserId: usr.ID, AuthModule: login.LDAPAuthModule}
|
authModuleQuery := &login.GetAuthInfoQuery{UserId: usr.ID, AuthModule: login.LDAPAuthModule}
|
||||||
|
|
@ -285,8 +201,7 @@ func (s *Service) PostSyncUserWithLDAP(c *contextmodel.ReqContext) response.Resp
|
||||||
return response.Error(500, "Failed to get user", err)
|
return response.Error(500, "Failed to get user", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ldapServer := newLDAP(ldapConfig.Servers)
|
userInfo, _, err := ldapClient.User(usr.Login)
|
||||||
userInfo, _, err := ldapServer.User(usr.Login)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, multildap.ErrDidNotFindUser) { // User was not in the LDAP server - we need to take action:
|
if errors.Is(err, multildap.ErrDidNotFindUser) { // User was not in the LDAP server - we need to take action:
|
||||||
if s.cfg.AdminUser == usr.Login { // User is *the* Grafana Admin. We cannot disable it.
|
if s.cfg.AdminUser == usr.Login { // User is *the* Grafana Admin. We cannot disable it.
|
||||||
|
|
@ -351,20 +266,14 @@ func (s *Service) GetUserFromLDAP(c *contextmodel.ReqContext) response.Response
|
||||||
return response.Error(http.StatusBadRequest, "LDAP is not enabled", nil)
|
return response.Error(http.StatusBadRequest, "LDAP is not enabled", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
ldapConfig, err := getLDAPConfig(s.cfg)
|
ldapClient := s.ldapService.Client()
|
||||||
if err != nil {
|
|
||||||
return response.Error(http.StatusBadRequest, "Failed to obtain the LDAP configuration", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
multiLDAP := newLDAP(ldapConfig.Servers)
|
|
||||||
|
|
||||||
username := web.Params(c.Req)[":username"]
|
username := web.Params(c.Req)[":username"]
|
||||||
|
|
||||||
if len(username) == 0 {
|
if len(username) == 0 {
|
||||||
return response.Error(http.StatusBadRequest, "Validation error. You must specify an username", nil)
|
return response.Error(http.StatusBadRequest, "Validation error. You must specify an username", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
user, serverConfig, err := multiLDAP.User(username)
|
user, serverConfig, err := ldapClient.User(username)
|
||||||
if user == nil || err != nil {
|
if user == nil || err != nil {
|
||||||
return response.Error(http.StatusNotFound, "No user was found in the LDAP server(s) with that username", err)
|
return response.Error(http.StatusNotFound, "No user was found in the LDAP server(s) with that username", err)
|
||||||
}
|
}
|
||||||
|
|
@ -409,7 +318,7 @@ func (s *Service) GetUserFromLDAP(c *contextmodel.ReqContext) response.Response
|
||||||
}
|
}
|
||||||
|
|
||||||
s.log.Debug("mapping org roles", "orgsRoles", u.OrgRoles)
|
s.log.Debug("mapping org roles", "orgsRoles", u.OrgRoles)
|
||||||
if err := u.FetchOrgs(c.Req.Context(), s.orgService); err != nil {
|
if err := u.fetchOrgs(c.Req.Context(), s.orgService); err != nil {
|
||||||
return response.Error(http.StatusBadRequest, "An organization was not found - Please verify your LDAP configuration", err)
|
return response.Error(http.StatusBadRequest, "An organization was not found - Please verify your LDAP configuration", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -435,16 +344,40 @@ func splitName(name string) (string, string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// swagger:parameters getUserFromLDAP
|
// fetchOrgs fetches the organization(s) information by executing a single query to the database. Then, populating the DTO with the information retrieved.
|
||||||
type GetLDAPUserParams struct {
|
func (user *LDAPUserDTO) fetchOrgs(ctx context.Context, orga org.Service) error {
|
||||||
// in:path
|
orgIds := []int64{}
|
||||||
// required:true
|
|
||||||
UserName string `json:"user_name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// swagger:parameters postSyncUserWithLDAP
|
for _, or := range user.OrgRoles {
|
||||||
type SyncLDAPUserParams struct {
|
orgIds = append(orgIds, or.OrgId)
|
||||||
// in:path
|
}
|
||||||
// required:true
|
|
||||||
UserID int64 `json:"user_id"`
|
q := &org.SearchOrgsQuery{}
|
||||||
|
q.IDs = orgIds
|
||||||
|
|
||||||
|
result, err := orga.Search(ctx, q)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
orgNamesById := map[int64]string{}
|
||||||
|
for _, org := range result {
|
||||||
|
orgNamesById[org.ID] = org.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, orgDTO := range user.OrgRoles {
|
||||||
|
if orgDTO.OrgId < 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
orgName := orgNamesById[orgDTO.OrgId]
|
||||||
|
|
||||||
|
if orgName != "" {
|
||||||
|
user.OrgRoles[i].OrgName = orgName
|
||||||
|
} else {
|
||||||
|
return &OrganizationNotFoundError{orgDTO.OrgId}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import (
|
||||||
"github.com/grafana/grafana/pkg/services/auth/authtest"
|
"github.com/grafana/grafana/pkg/services/auth/authtest"
|
||||||
"github.com/grafana/grafana/pkg/services/ldap"
|
"github.com/grafana/grafana/pkg/services/ldap"
|
||||||
"github.com/grafana/grafana/pkg/services/ldap/multildap"
|
"github.com/grafana/grafana/pkg/services/ldap/multildap"
|
||||||
|
"github.com/grafana/grafana/pkg/services/ldap/service"
|
||||||
"github.com/grafana/grafana/pkg/services/login"
|
"github.com/grafana/grafana/pkg/services/login"
|
||||||
"github.com/grafana/grafana/pkg/services/login/logintest"
|
"github.com/grafana/grafana/pkg/services/login/logintest"
|
||||||
"github.com/grafana/grafana/pkg/services/org"
|
"github.com/grafana/grafana/pkg/services/org"
|
||||||
|
|
@ -69,6 +70,7 @@ func setupAPITest(t *testing.T, opts ...func(a *Service)) (*Service, *webtest.Se
|
||||||
ldap.ProvideGroupsService(),
|
ldap.ProvideGroupsService(),
|
||||||
&logintest.LoginServiceFake{},
|
&logintest.LoginServiceFake{},
|
||||||
&orgtest.FakeOrgService{},
|
&orgtest.FakeOrgService{},
|
||||||
|
service.NewLDAPFakeService(),
|
||||||
authtest.NewFakeUserAuthTokenService(),
|
authtest.NewFakeUserAuthTokenService(),
|
||||||
supportbundlestest.NewFakeBundleService(),
|
supportbundlestest.NewFakeBundleService(),
|
||||||
)
|
)
|
||||||
|
|
@ -83,20 +85,16 @@ func setupAPITest(t *testing.T, opts ...func(a *Service)) (*Service, *webtest.Se
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetUserFromLDAPAPIEndpoint_UserNotFound(t *testing.T) {
|
func TestGetUserFromLDAPAPIEndpoint_UserNotFound(t *testing.T) {
|
||||||
getLDAPConfig = func(*setting.Cfg) (*ldap.Config, error) {
|
|
||||||
return &ldap.Config{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
newLDAP = func(_ []*ldap.ServerConfig) multildap.IMultiLDAP {
|
|
||||||
return &LDAPMock{
|
|
||||||
UserSearchResult: nil,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_, server := setupAPITest(t, func(a *Service) {
|
_, server := setupAPITest(t, func(a *Service) {
|
||||||
a.orgService = &orgtest.FakeOrgService{
|
a.orgService = &orgtest.FakeOrgService{
|
||||||
ExpectedOrgs: []*org.OrgDTO{},
|
ExpectedOrgs: []*org.OrgDTO{},
|
||||||
}
|
}
|
||||||
|
a.ldapService = &service.LDAPFakeService{
|
||||||
|
ExpectedClient: &LDAPMock{
|
||||||
|
UserSearchResult: nil,
|
||||||
|
},
|
||||||
|
ExpectedConfig: &ldap.Config{},
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
req := server.NewGetRequest("/api/admin/ldap/user-that-does-not-exist")
|
req := server.NewGetRequest("/api/admin/ldap/user-that-does-not-exist")
|
||||||
|
|
@ -147,13 +145,6 @@ func TestGetUserFromLDAPAPIEndpoint_OrgNotfound(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
newLDAP = func(_ []*ldap.ServerConfig) multildap.IMultiLDAP {
|
|
||||||
return &LDAPMock{
|
|
||||||
UserSearchResult: userSearchResult,
|
|
||||||
UserSearchConfig: userSearchConfig,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mockOrgSearchResult := []*org.OrgDTO{
|
mockOrgSearchResult := []*org.OrgDTO{
|
||||||
{ID: 1, Name: "Main Org."},
|
{ID: 1, Name: "Main Org."},
|
||||||
}
|
}
|
||||||
|
|
@ -162,12 +153,15 @@ func TestGetUserFromLDAPAPIEndpoint_OrgNotfound(t *testing.T) {
|
||||||
a.orgService = &orgtest.FakeOrgService{
|
a.orgService = &orgtest.FakeOrgService{
|
||||||
ExpectedOrgs: mockOrgSearchResult,
|
ExpectedOrgs: mockOrgSearchResult,
|
||||||
}
|
}
|
||||||
|
a.ldapService = &service.LDAPFakeService{
|
||||||
|
ExpectedClient: &LDAPMock{
|
||||||
|
UserSearchResult: userSearchResult,
|
||||||
|
UserSearchConfig: userSearchConfig,
|
||||||
|
},
|
||||||
|
ExpectedConfig: &ldap.Config{},
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
getLDAPConfig = func(*setting.Cfg) (*ldap.Config, error) {
|
|
||||||
return &ldap.Config{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
req := server.NewGetRequest("/api/admin/ldap/johndoe")
|
req := server.NewGetRequest("/api/admin/ldap/johndoe")
|
||||||
webtest.RequestWithSignedInUser(req, &user.SignedInUser{
|
webtest.RequestWithSignedInUser(req, &user.SignedInUser{
|
||||||
OrgID: 1,
|
OrgID: 1,
|
||||||
|
|
@ -225,21 +219,17 @@ func TestGetUserFromLDAPAPIEndpoint(t *testing.T) {
|
||||||
{ID: 1, Name: "Main Org."},
|
{ID: 1, Name: "Main Org."},
|
||||||
}
|
}
|
||||||
|
|
||||||
getLDAPConfig = func(*setting.Cfg) (*ldap.Config, error) {
|
|
||||||
return &ldap.Config{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
newLDAP = func(_ []*ldap.ServerConfig) multildap.IMultiLDAP {
|
|
||||||
return &LDAPMock{
|
|
||||||
UserSearchResult: userSearchResult,
|
|
||||||
UserSearchConfig: userSearchConfig,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_, server := setupAPITest(t, func(a *Service) {
|
_, server := setupAPITest(t, func(a *Service) {
|
||||||
a.orgService = &orgtest.FakeOrgService{
|
a.orgService = &orgtest.FakeOrgService{
|
||||||
ExpectedOrgs: mockOrgSearchResult,
|
ExpectedOrgs: mockOrgSearchResult,
|
||||||
}
|
}
|
||||||
|
a.ldapService = &service.LDAPFakeService{
|
||||||
|
ExpectedClient: &LDAPMock{
|
||||||
|
UserSearchResult: userSearchResult,
|
||||||
|
UserSearchConfig: userSearchConfig,
|
||||||
|
},
|
||||||
|
ExpectedConfig: &ldap.Config{},
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
req := server.NewGetRequest("/api/admin/ldap/johndoe")
|
req := server.NewGetRequest("/api/admin/ldap/johndoe")
|
||||||
|
|
@ -314,21 +304,17 @@ func TestGetUserFromLDAPAPIEndpoint_WithTeamHandler(t *testing.T) {
|
||||||
{ID: 1, Name: "Main Org."},
|
{ID: 1, Name: "Main Org."},
|
||||||
}
|
}
|
||||||
|
|
||||||
getLDAPConfig = func(*setting.Cfg) (*ldap.Config, error) {
|
|
||||||
return &ldap.Config{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
newLDAP = func(_ []*ldap.ServerConfig) multildap.IMultiLDAP {
|
|
||||||
return &LDAPMock{
|
|
||||||
UserSearchResult: userSearchResult,
|
|
||||||
UserSearchConfig: userSearchConfig,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_, server := setupAPITest(t, func(a *Service) {
|
_, server := setupAPITest(t, func(a *Service) {
|
||||||
a.orgService = &orgtest.FakeOrgService{
|
a.orgService = &orgtest.FakeOrgService{
|
||||||
ExpectedOrgs: mockOrgSearchResult,
|
ExpectedOrgs: mockOrgSearchResult,
|
||||||
}
|
}
|
||||||
|
a.ldapService = &service.LDAPFakeService{
|
||||||
|
ExpectedClient: &LDAPMock{
|
||||||
|
UserSearchResult: userSearchResult,
|
||||||
|
UserSearchConfig: userSearchConfig,
|
||||||
|
},
|
||||||
|
ExpectedConfig: &ldap.Config{},
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
req := server.NewGetRequest("/api/admin/ldap/johndoe")
|
req := server.NewGetRequest("/api/admin/ldap/johndoe")
|
||||||
|
|
@ -378,15 +364,11 @@ func TestGetLDAPStatusAPIEndpoint(t *testing.T) {
|
||||||
{Host: "10.0.0.5", Port: 361, Available: false, Error: errors.New("something is awfully wrong")},
|
{Host: "10.0.0.5", Port: 361, Available: false, Error: errors.New("something is awfully wrong")},
|
||||||
}
|
}
|
||||||
|
|
||||||
getLDAPConfig = func(*setting.Cfg) (*ldap.Config, error) {
|
|
||||||
return &ldap.Config{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
newLDAP = func(_ []*ldap.ServerConfig) multildap.IMultiLDAP {
|
|
||||||
return &LDAPMock{}
|
|
||||||
}
|
|
||||||
|
|
||||||
_, server := setupAPITest(t, func(a *Service) {
|
_, server := setupAPITest(t, func(a *Service) {
|
||||||
|
a.ldapService = &service.LDAPFakeService{
|
||||||
|
ExpectedClient: &LDAPMock{},
|
||||||
|
ExpectedConfig: &ldap.Config{},
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
req := server.NewGetRequest("/api/admin/ldap/status")
|
req := server.NewGetRequest("/api/admin/ldap/status")
|
||||||
|
|
@ -418,18 +400,14 @@ func TestPostSyncUserWithLDAPAPIEndpoint_Success(t *testing.T) {
|
||||||
userServiceMock := usertest.NewUserServiceFake()
|
userServiceMock := usertest.NewUserServiceFake()
|
||||||
userServiceMock.ExpectedUser = &user.User{Login: "ldap-daniel", ID: 34}
|
userServiceMock.ExpectedUser = &user.User{Login: "ldap-daniel", ID: 34}
|
||||||
|
|
||||||
getLDAPConfig = func(*setting.Cfg) (*ldap.Config, error) {
|
|
||||||
return &ldap.Config{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
newLDAP = func(_ []*ldap.ServerConfig) multildap.IMultiLDAP {
|
|
||||||
return &LDAPMock{UserSearchResult: &login.ExternalUserInfo{
|
|
||||||
Login: "ldap-daniel",
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
_, server := setupAPITest(t, func(a *Service) {
|
_, server := setupAPITest(t, func(a *Service) {
|
||||||
a.userService = userServiceMock
|
a.userService = userServiceMock
|
||||||
|
a.ldapService = &service.LDAPFakeService{
|
||||||
|
ExpectedClient: &LDAPMock{UserSearchResult: &login.ExternalUserInfo{
|
||||||
|
Login: "ldap-daniel",
|
||||||
|
}},
|
||||||
|
ExpectedConfig: &ldap.Config{},
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
req := server.NewPostRequest("/api/admin/ldap/sync/34", nil)
|
req := server.NewPostRequest("/api/admin/ldap/sync/34", nil)
|
||||||
|
|
@ -459,16 +437,12 @@ func TestPostSyncUserWithLDAPAPIEndpoint_WhenUserNotFound(t *testing.T) {
|
||||||
userServiceMock := usertest.NewUserServiceFake()
|
userServiceMock := usertest.NewUserServiceFake()
|
||||||
userServiceMock.ExpectedError = user.ErrUserNotFound
|
userServiceMock.ExpectedError = user.ErrUserNotFound
|
||||||
|
|
||||||
getLDAPConfig = func(*setting.Cfg) (*ldap.Config, error) {
|
|
||||||
return &ldap.Config{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
newLDAP = func(_ []*ldap.ServerConfig) multildap.IMultiLDAP {
|
|
||||||
return &LDAPMock{}
|
|
||||||
}
|
|
||||||
|
|
||||||
_, server := setupAPITest(t, func(a *Service) {
|
_, server := setupAPITest(t, func(a *Service) {
|
||||||
a.userService = userServiceMock
|
a.userService = userServiceMock
|
||||||
|
a.ldapService = &service.LDAPFakeService{
|
||||||
|
ExpectedClient: &LDAPMock{},
|
||||||
|
ExpectedConfig: &ldap.Config{},
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
req := server.NewPostRequest("/api/admin/ldap/sync/34", nil)
|
req := server.NewPostRequest("/api/admin/ldap/sync/34", nil)
|
||||||
|
|
@ -498,17 +472,13 @@ func TestPostSyncUserWithLDAPAPIEndpoint_WhenGrafanaAdmin(t *testing.T) {
|
||||||
userServiceMock := usertest.NewUserServiceFake()
|
userServiceMock := usertest.NewUserServiceFake()
|
||||||
userServiceMock.ExpectedUser = &user.User{Login: "ldap-daniel", ID: 34}
|
userServiceMock.ExpectedUser = &user.User{Login: "ldap-daniel", ID: 34}
|
||||||
|
|
||||||
getLDAPConfig = func(*setting.Cfg) (*ldap.Config, error) {
|
|
||||||
return &ldap.Config{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
newLDAP = func(_ []*ldap.ServerConfig) multildap.IMultiLDAP {
|
|
||||||
return &LDAPMock{UserSearchError: multildap.ErrDidNotFindUser}
|
|
||||||
}
|
|
||||||
|
|
||||||
_, server := setupAPITest(t, func(a *Service) {
|
_, server := setupAPITest(t, func(a *Service) {
|
||||||
a.userService = userServiceMock
|
a.userService = userServiceMock
|
||||||
a.cfg.AdminUser = "ldap-daniel"
|
a.cfg.AdminUser = "ldap-daniel"
|
||||||
|
a.ldapService = &service.LDAPFakeService{
|
||||||
|
ExpectedClient: &LDAPMock{UserSearchError: multildap.ErrDidNotFindUser},
|
||||||
|
ExpectedConfig: &ldap.Config{},
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
req := server.NewPostRequest("/api/admin/ldap/sync/34", nil)
|
req := server.NewPostRequest("/api/admin/ldap/sync/34", nil)
|
||||||
|
|
@ -536,17 +506,13 @@ func TestPostSyncUserWithLDAPAPIEndpoint_WhenUserNotInLDAP(t *testing.T) {
|
||||||
userServiceMock := usertest.NewUserServiceFake()
|
userServiceMock := usertest.NewUserServiceFake()
|
||||||
userServiceMock.ExpectedUser = &user.User{Login: "ldap-daniel", ID: 34}
|
userServiceMock.ExpectedUser = &user.User{Login: "ldap-daniel", ID: 34}
|
||||||
|
|
||||||
getLDAPConfig = func(*setting.Cfg) (*ldap.Config, error) {
|
|
||||||
return &ldap.Config{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
newLDAP = func(_ []*ldap.ServerConfig) multildap.IMultiLDAP {
|
|
||||||
return &LDAPMock{UserSearchError: multildap.ErrDidNotFindUser}
|
|
||||||
}
|
|
||||||
|
|
||||||
_, server := setupAPITest(t, func(a *Service) {
|
_, server := setupAPITest(t, func(a *Service) {
|
||||||
a.userService = userServiceMock
|
a.userService = userServiceMock
|
||||||
a.authInfoService = &logintest.AuthInfoServiceFake{ExpectedExternalUser: &login.ExternalUserInfo{IsDisabled: true, UserId: 34}}
|
a.authInfoService = &logintest.AuthInfoServiceFake{ExpectedExternalUser: &login.ExternalUserInfo{IsDisabled: true, UserId: 34}}
|
||||||
|
a.ldapService = &service.LDAPFakeService{
|
||||||
|
ExpectedClient: &LDAPMock{UserSearchError: multildap.ErrDidNotFindUser},
|
||||||
|
ExpectedConfig: &ldap.Config{},
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
req := server.NewPostRequest("/api/admin/ldap/sync/34", nil)
|
req := server.NewPostRequest("/api/admin/ldap/sync/34", nil)
|
||||||
|
|
@ -589,18 +555,6 @@ search_base_dns = ["dc=grafana,dc=org"]`)
|
||||||
errF = f.Close()
|
errF = f.Close()
|
||||||
require.NoError(t, errF)
|
require.NoError(t, errF)
|
||||||
|
|
||||||
getLDAPConfig = func(*setting.Cfg) (*ldap.Config, error) {
|
|
||||||
return &ldap.Config{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
newLDAP = func(_ []*ldap.ServerConfig) multildap.IMultiLDAP {
|
|
||||||
return &LDAPMock{
|
|
||||||
UserSearchResult: &login.ExternalUserInfo{
|
|
||||||
Login: "ldap-daniel",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type testCase struct {
|
type testCase struct {
|
||||||
desc string
|
desc string
|
||||||
method string
|
method string
|
||||||
|
|
@ -688,6 +642,12 @@ search_base_dns = ["dc=grafana,dc=org"]`)
|
||||||
_, server := setupAPITest(t, func(a *Service) {
|
_, server := setupAPITest(t, func(a *Service) {
|
||||||
a.userService = &usertest.FakeUserService{ExpectedUser: &user.User{Login: "ldap-daniel", ID: 1}}
|
a.userService = &usertest.FakeUserService{ExpectedUser: &user.User{Login: "ldap-daniel", ID: 1}}
|
||||||
a.cfg.LDAPConfigFilePath = ldapConfigFile
|
a.cfg.LDAPConfigFilePath = ldapConfigFile
|
||||||
|
a.ldapService = &service.LDAPFakeService{
|
||||||
|
ExpectedClient: &LDAPMock{UserSearchResult: &login.ExternalUserInfo{
|
||||||
|
Login: "ldap-daniel",
|
||||||
|
}},
|
||||||
|
ExpectedConfig: &ldap.Config{},
|
||||||
|
}
|
||||||
})
|
})
|
||||||
// Add minimal setup to pass handler
|
// Add minimal setup to pass handler
|
||||||
res, err := server.Send(
|
res, err := server.Send(
|
||||||
|
|
|
||||||
|
|
@ -9,19 +9,17 @@ import (
|
||||||
"github.com/BurntSushi/toml"
|
"github.com/BurntSushi/toml"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/services/supportbundles"
|
"github.com/grafana/grafana/pkg/services/supportbundles"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Service) supportBundleCollector(context.Context) (*supportbundles.SupportItem, error) {
|
func (s *Service) supportBundleCollector(context.Context) (*supportbundles.SupportItem, error) {
|
||||||
bWriter := bytes.NewBuffer(nil)
|
bWriter := bytes.NewBuffer(nil)
|
||||||
bWriter.WriteString("# LDAP information\n\n")
|
bWriter.WriteString("# LDAP information\n\n")
|
||||||
|
|
||||||
ldapConfig, err := getLDAPConfig(s.cfg)
|
ldapConfig := s.ldapService.Config()
|
||||||
|
|
||||||
if ldapConfig != nil {
|
if ldapConfig != nil {
|
||||||
bWriter.WriteString("## LDAP Status\n")
|
bWriter.WriteString("## LDAP Status\n")
|
||||||
|
|
||||||
ldapClient := newLDAP(ldapConfig.Servers)
|
ldapClient := s.ldapService.Client()
|
||||||
|
|
||||||
ldapStatus, err := ldapClient.Ping()
|
ldapStatus, err := ldapClient.Ping()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -66,7 +64,7 @@ func (s *Service) supportBundleCollector(context.Context) (*supportbundles.Suppo
|
||||||
errM := toml.NewEncoder(bWriter).Encode(ldapConfig)
|
errM := toml.NewEncoder(bWriter).Encode(ldapConfig)
|
||||||
if errM != nil {
|
if errM != nil {
|
||||||
bWriter.WriteString(
|
bWriter.WriteString(
|
||||||
fmt.Sprintf("Unable to encode LDAP configuration \n Err: %s", err))
|
fmt.Sprintf("Unable to encode LDAP configuration \n Err: %s", errM))
|
||||||
}
|
}
|
||||||
bWriter.WriteString("```\n\n")
|
bWriter.WriteString("```\n\n")
|
||||||
|
|
||||||
|
|
@ -77,9 +75,9 @@ func (s *Service) supportBundleCollector(context.Context) (*supportbundles.Suppo
|
||||||
bWriter.WriteString(fmt.Sprintf("enabled = %v\n", s.cfg.LDAPEnabled))
|
bWriter.WriteString(fmt.Sprintf("enabled = %v\n", s.cfg.LDAPEnabled))
|
||||||
bWriter.WriteString(fmt.Sprintf("config_file = %s\n", s.cfg.LDAPConfigFilePath))
|
bWriter.WriteString(fmt.Sprintf("config_file = %s\n", s.cfg.LDAPConfigFilePath))
|
||||||
bWriter.WriteString(fmt.Sprintf("allow_sign_up = %v\n", s.cfg.LDAPAllowSignup))
|
bWriter.WriteString(fmt.Sprintf("allow_sign_up = %v\n", s.cfg.LDAPAllowSignup))
|
||||||
bWriter.WriteString(fmt.Sprintf("sync_cron = %s\n", setting.LDAPSyncCron))
|
bWriter.WriteString(fmt.Sprintf("sync_cron = %s\n", s.cfg.LDAPSyncCron))
|
||||||
bWriter.WriteString(fmt.Sprintf("active_sync_enabled = %v\n", setting.LDAPActiveSyncEnabled))
|
bWriter.WriteString(fmt.Sprintf("active_sync_enabled = %v\n", s.cfg.LDAPActiveSyncEnabled))
|
||||||
bWriter.WriteString(fmt.Sprintf("skip_org_role_sync = %v\n", setting.LDAPSkipOrgRoleSync))
|
bWriter.WriteString(fmt.Sprintf("skip_org_role_sync = %v\n", s.cfg.LDAPSkipOrgRoleSync))
|
||||||
|
|
||||||
bWriter.WriteString("```\n\n")
|
bWriter.WriteString("```\n\n")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import (
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/services/login"
|
"github.com/grafana/grafana/pkg/services/login"
|
||||||
"github.com/grafana/grafana/pkg/services/org"
|
"github.com/grafana/grafana/pkg/services/org"
|
||||||
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
// IConnection is interface for LDAP connection manipulation
|
// IConnection is interface for LDAP connection manipulation
|
||||||
|
|
@ -42,6 +43,7 @@ type IServer interface {
|
||||||
|
|
||||||
// Server is basic struct of LDAP authorization
|
// Server is basic struct of LDAP authorization
|
||||||
type Server struct {
|
type Server struct {
|
||||||
|
cfg *setting.Cfg
|
||||||
Config *ServerConfig
|
Config *ServerConfig
|
||||||
Connection IConnection
|
Connection IConnection
|
||||||
log log.Logger
|
log log.Logger
|
||||||
|
|
@ -82,9 +84,10 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// New creates the new LDAP connection
|
// New creates the new LDAP connection
|
||||||
func New(config *ServerConfig) IServer {
|
func New(config *ServerConfig, cfg *setting.Cfg) IServer {
|
||||||
return &Server{
|
return &Server{
|
||||||
Config: config,
|
Config: config,
|
||||||
|
cfg: cfg,
|
||||||
log: log.New("ldap"),
|
log: log.New("ldap"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -361,9 +364,9 @@ func (server *Server) users(logins []string) (
|
||||||
// If there are no ldap group mappings access is true
|
// If there are no ldap group mappings access is true
|
||||||
// otherwise a single group must match
|
// otherwise a single group must match
|
||||||
func (server *Server) validateGrafanaUser(user *login.ExternalUserInfo) error {
|
func (server *Server) validateGrafanaUser(user *login.ExternalUserInfo) error {
|
||||||
if !SkipOrgRoleSync() && len(server.Config.Groups) > 0 &&
|
if !server.cfg.LDAPSkipOrgRoleSync && len(server.Config.Groups) > 0 &&
|
||||||
(len(user.OrgRoles) == 0 && (user.IsGrafanaAdmin == nil || !*user.IsGrafanaAdmin)) {
|
(len(user.OrgRoles) == 0 && (user.IsGrafanaAdmin == nil || !*user.IsGrafanaAdmin)) {
|
||||||
server.log.Error(
|
server.log.Warn(
|
||||||
"User does not belong in any of the specified LDAP groups",
|
"User does not belong in any of the specified LDAP groups",
|
||||||
"username", user.Login,
|
"username", user.Login,
|
||||||
"groups", user.Groups,
|
"groups", user.Groups,
|
||||||
|
|
@ -446,7 +449,7 @@ func (server *Server) buildGrafanaUser(user *ldap.Entry) (*login.ExternalUserInf
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skipping org role sync
|
// Skipping org role sync
|
||||||
if SkipOrgRoleSync() {
|
if server.cfg.LDAPSkipOrgRoleSync {
|
||||||
server.log.Debug("skipping organization role mapping.")
|
server.log.Debug("skipping organization role mapping.")
|
||||||
return extUser, nil
|
return extUser, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import (
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/services/login"
|
"github.com/grafana/grafana/pkg/services/login"
|
||||||
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
var defaultLogin = &login.LoginUserQuery{
|
var defaultLogin = &login.LoginUserQuery{
|
||||||
|
|
@ -29,7 +30,11 @@ func TestServer_Login_UserBind_Fail(t *testing.T) {
|
||||||
ResultCode: 49,
|
ResultCode: 49,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cfg := setting.NewCfg()
|
||||||
|
cfg.LDAPEnabled = true
|
||||||
server := &Server{
|
server := &Server{
|
||||||
|
cfg: cfg,
|
||||||
Config: &ServerConfig{
|
Config: &ServerConfig{
|
||||||
SearchBaseDNs: []string{"BaseDNHere"},
|
SearchBaseDNs: []string{"BaseDNHere"},
|
||||||
},
|
},
|
||||||
|
|
@ -99,7 +104,12 @@ func TestServer_Login_ValidCredentials(t *testing.T) {
|
||||||
connection.BindProvider = func(username, password string) error {
|
connection.BindProvider = func(username, password string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cfg := setting.NewCfg()
|
||||||
|
cfg.LDAPEnabled = true
|
||||||
|
|
||||||
server := &Server{
|
server := &Server{
|
||||||
|
cfg: cfg,
|
||||||
Config: &ServerConfig{
|
Config: &ServerConfig{
|
||||||
Attr: AttributeMap{
|
Attr: AttributeMap{
|
||||||
Username: "username",
|
Username: "username",
|
||||||
|
|
@ -131,7 +141,12 @@ func TestServer_Login_UnauthenticatedBind(t *testing.T) {
|
||||||
connection.UnauthenticatedBindProvider = func() error {
|
connection.UnauthenticatedBindProvider = func() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cfg := setting.NewCfg()
|
||||||
|
cfg.LDAPEnabled = true
|
||||||
|
|
||||||
server := &Server{
|
server := &Server{
|
||||||
|
cfg: cfg,
|
||||||
Config: &ServerConfig{
|
Config: &ServerConfig{
|
||||||
SearchBaseDNs: []string{"BaseDNHere"},
|
SearchBaseDNs: []string{"BaseDNHere"},
|
||||||
},
|
},
|
||||||
|
|
@ -173,7 +188,12 @@ func TestServer_Login_AuthenticatedBind(t *testing.T) {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cfg := setting.NewCfg()
|
||||||
|
cfg.LDAPEnabled = true
|
||||||
|
|
||||||
server := &Server{
|
server := &Server{
|
||||||
|
cfg: cfg,
|
||||||
Config: &ServerConfig{
|
Config: &ServerConfig{
|
||||||
BindDN: "killa",
|
BindDN: "killa",
|
||||||
BindPassword: "gorilla",
|
BindPassword: "gorilla",
|
||||||
|
|
@ -211,7 +231,12 @@ func TestServer_Login_UserWildcardBind(t *testing.T) {
|
||||||
authBindPassword = pass
|
authBindPassword = pass
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cfg := setting.NewCfg()
|
||||||
|
cfg.LDAPEnabled = true
|
||||||
|
|
||||||
server := &Server{
|
server := &Server{
|
||||||
|
cfg: cfg,
|
||||||
Config: &ServerConfig{
|
Config: &ServerConfig{
|
||||||
BindDN: "cn=%s,ou=users,dc=grafana,dc=org",
|
BindDN: "cn=%s,ou=users,dc=grafana,dc=org",
|
||||||
SearchBaseDNs: []string{"BaseDNHere"},
|
SearchBaseDNs: []string{"BaseDNHere"},
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/services/login"
|
"github.com/grafana/grafana/pkg/services/login"
|
||||||
"github.com/grafana/grafana/pkg/services/org"
|
"github.com/grafana/grafana/pkg/services/org"
|
||||||
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestServer_getSearchRequest(t *testing.T) {
|
func TestServer_getSearchRequest(t *testing.T) {
|
||||||
|
|
@ -52,7 +53,11 @@ func TestServer_getSearchRequest(t *testing.T) {
|
||||||
|
|
||||||
func TestSerializeUsers(t *testing.T) {
|
func TestSerializeUsers(t *testing.T) {
|
||||||
t.Run("simple case", func(t *testing.T) {
|
t.Run("simple case", func(t *testing.T) {
|
||||||
|
cfg := setting.NewCfg()
|
||||||
|
cfg.LDAPEnabled = true
|
||||||
|
|
||||||
server := &Server{
|
server := &Server{
|
||||||
|
cfg: cfg,
|
||||||
Config: &ServerConfig{
|
Config: &ServerConfig{
|
||||||
Attr: AttributeMap{
|
Attr: AttributeMap{
|
||||||
Username: "username",
|
Username: "username",
|
||||||
|
|
@ -87,7 +92,11 @@ func TestSerializeUsers(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("without lastname", func(t *testing.T) {
|
t.Run("without lastname", func(t *testing.T) {
|
||||||
|
cfg := setting.NewCfg()
|
||||||
|
cfg.LDAPEnabled = true
|
||||||
|
|
||||||
server := &Server{
|
server := &Server{
|
||||||
|
cfg: cfg,
|
||||||
Config: &ServerConfig{
|
Config: &ServerConfig{
|
||||||
Attr: AttributeMap{
|
Attr: AttributeMap{
|
||||||
Username: "username",
|
Username: "username",
|
||||||
|
|
@ -120,7 +129,11 @@ func TestSerializeUsers(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("mark user without matching group as disabled", func(t *testing.T) {
|
t.Run("mark user without matching group as disabled", func(t *testing.T) {
|
||||||
|
cfg := setting.NewCfg()
|
||||||
|
cfg.LDAPEnabled = true
|
||||||
|
|
||||||
server := &Server{
|
server := &Server{
|
||||||
|
cfg: cfg,
|
||||||
Config: &ServerConfig{
|
Config: &ServerConfig{
|
||||||
Groups: []*GroupToOrgRole{{
|
Groups: []*GroupToOrgRole{{
|
||||||
GroupDN: "foo",
|
GroupDN: "foo",
|
||||||
|
|
@ -150,7 +163,11 @@ func TestSerializeUsers(t *testing.T) {
|
||||||
|
|
||||||
func TestServer_validateGrafanaUser(t *testing.T) {
|
func TestServer_validateGrafanaUser(t *testing.T) {
|
||||||
t.Run("no group config", func(t *testing.T) {
|
t.Run("no group config", func(t *testing.T) {
|
||||||
|
cfg := setting.NewCfg()
|
||||||
|
cfg.LDAPEnabled = true
|
||||||
|
|
||||||
server := &Server{
|
server := &Server{
|
||||||
|
cfg: cfg,
|
||||||
Config: &ServerConfig{
|
Config: &ServerConfig{
|
||||||
Groups: []*GroupToOrgRole{},
|
Groups: []*GroupToOrgRole{},
|
||||||
},
|
},
|
||||||
|
|
@ -166,7 +183,11 @@ func TestServer_validateGrafanaUser(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("user in group", func(t *testing.T) {
|
t.Run("user in group", func(t *testing.T) {
|
||||||
|
cfg := setting.NewCfg()
|
||||||
|
cfg.LDAPEnabled = true
|
||||||
|
|
||||||
server := &Server{
|
server := &Server{
|
||||||
|
cfg: cfg,
|
||||||
Config: &ServerConfig{
|
Config: &ServerConfig{
|
||||||
Groups: []*GroupToOrgRole{
|
Groups: []*GroupToOrgRole{
|
||||||
{
|
{
|
||||||
|
|
@ -189,7 +210,11 @@ func TestServer_validateGrafanaUser(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("user not in group", func(t *testing.T) {
|
t.Run("user not in group", func(t *testing.T) {
|
||||||
|
cfg := setting.NewCfg()
|
||||||
|
cfg.LDAPEnabled = true
|
||||||
|
|
||||||
server := &Server{
|
server := &Server{
|
||||||
|
cfg: cfg,
|
||||||
Config: &ServerConfig{
|
Config: &ServerConfig{
|
||||||
Groups: []*GroupToOrgRole{
|
Groups: []*GroupToOrgRole{
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ func TestNew(t *testing.T) {
|
||||||
result := New(&ServerConfig{
|
result := New(&ServerConfig{
|
||||||
Attr: AttributeMap{},
|
Attr: AttributeMap{},
|
||||||
SearchBaseDNs: []string{"BaseDNHere"},
|
SearchBaseDNs: []string{"BaseDNHere"},
|
||||||
})
|
}, &setting.Cfg{})
|
||||||
|
|
||||||
assert.Implements(t, (*IServer)(nil), result)
|
assert.Implements(t, (*IServer)(nil), result)
|
||||||
}
|
}
|
||||||
|
|
@ -67,7 +67,11 @@ func TestServer_Users(t *testing.T) {
|
||||||
conn.setSearchResult(&result)
|
conn.setSearchResult(&result)
|
||||||
|
|
||||||
// Set up attribute map without surname and email
|
// Set up attribute map without surname and email
|
||||||
|
cfg := setting.NewCfg()
|
||||||
|
cfg.LDAPEnabled = true
|
||||||
|
|
||||||
server := &Server{
|
server := &Server{
|
||||||
|
cfg: cfg,
|
||||||
Config: &ServerConfig{
|
Config: &ServerConfig{
|
||||||
Attr: AttributeMap{
|
Attr: AttributeMap{
|
||||||
Username: "username",
|
Username: "username",
|
||||||
|
|
@ -160,6 +164,7 @@ func TestServer_Users(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
server := &Server{
|
server := &Server{
|
||||||
|
cfg: setting.NewCfg(),
|
||||||
Config: &ServerConfig{
|
Config: &ServerConfig{
|
||||||
Attr: AttributeMap{
|
Attr: AttributeMap{
|
||||||
Username: "username",
|
Username: "username",
|
||||||
|
|
@ -206,7 +211,11 @@ func TestServer_Users(t *testing.T) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
cfg := setting.NewCfg()
|
||||||
|
cfg.LDAPEnabled = true
|
||||||
|
|
||||||
server := &Server{
|
server := &Server{
|
||||||
|
cfg: cfg,
|
||||||
Config: &ServerConfig{
|
Config: &ServerConfig{
|
||||||
Attr: AttributeMap{
|
Attr: AttributeMap{
|
||||||
Username: "username",
|
Username: "username",
|
||||||
|
|
@ -279,7 +288,11 @@ func TestServer_Users(t *testing.T) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
cfg := setting.NewCfg()
|
||||||
|
cfg.LDAPEnabled = true
|
||||||
|
|
||||||
server := &Server{
|
server := &Server{
|
||||||
|
cfg: cfg,
|
||||||
Config: &ServerConfig{
|
Config: &ServerConfig{
|
||||||
Attr: AttributeMap{
|
Attr: AttributeMap{
|
||||||
Username: "username",
|
Username: "username",
|
||||||
|
|
@ -312,11 +325,7 @@ func TestServer_Users(t *testing.T) {
|
||||||
require.True(t, res[0].IsDisabled)
|
require.True(t, res[0].IsDisabled)
|
||||||
})
|
})
|
||||||
t.Run("skip org role sync", func(t *testing.T) {
|
t.Run("skip org role sync", func(t *testing.T) {
|
||||||
backup := setting.LDAPSkipOrgRoleSync
|
server.cfg.LDAPSkipOrgRoleSync = true
|
||||||
defer func() {
|
|
||||||
setting.LDAPSkipOrgRoleSync = backup
|
|
||||||
}()
|
|
||||||
setting.LDAPSkipOrgRoleSync = true
|
|
||||||
|
|
||||||
res, err := server.Users([]string{"groot"})
|
res, err := server.Users([]string{"groot"})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -327,6 +336,7 @@ func TestServer_Users(t *testing.T) {
|
||||||
require.False(t, res[0].IsDisabled)
|
require.False(t, res[0].IsDisabled)
|
||||||
})
|
})
|
||||||
t.Run("sync org role", func(t *testing.T) {
|
t.Run("sync org role", func(t *testing.T) {
|
||||||
|
server.cfg.LDAPSkipOrgRoleSync = false
|
||||||
res, err := server.Users([]string{"groot"})
|
res, err := server.Users([]string{"groot"})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, res, 1)
|
require.Len(t, res, 1)
|
||||||
|
|
|
||||||
|
|
@ -3,34 +3,29 @@ package multildap
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/services/ldap"
|
"github.com/grafana/grafana/pkg/services/ldap"
|
||||||
"github.com/grafana/grafana/pkg/services/login"
|
"github.com/grafana/grafana/pkg/services/login"
|
||||||
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
// logger to log
|
|
||||||
var logger = log.New("ldap")
|
|
||||||
|
|
||||||
// GetConfig gets LDAP config
|
// GetConfig gets LDAP config
|
||||||
var GetConfig = ldap.GetConfig
|
var GetConfig = ldap.GetConfig
|
||||||
|
|
||||||
// IsEnabled checks if LDAP is enabled
|
|
||||||
var IsEnabled = ldap.IsEnabled
|
|
||||||
|
|
||||||
// newLDAP return instance of the single LDAP server
|
// newLDAP return instance of the single LDAP server
|
||||||
var newLDAP = ldap.New
|
var newLDAP = ldap.New
|
||||||
|
|
||||||
// ErrInvalidCredentials is returned if username and password do not match
|
var (
|
||||||
var ErrInvalidCredentials = ldap.ErrInvalidCredentials
|
// 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 is returned when username hasn't been found (not username+password)
|
||||||
var ErrCouldNotFindUser = ldap.ErrCouldNotFindUser
|
ErrCouldNotFindUser = ldap.ErrCouldNotFindUser
|
||||||
|
// ErrNoLDAPServers is returned when there is no LDAP servers specified
|
||||||
// ErrNoLDAPServers is returned when there is no LDAP servers specified
|
ErrNoLDAPServers = errors.New("no LDAP servers are configured")
|
||||||
var ErrNoLDAPServers = errors.New("no LDAP servers are configured")
|
// ErrDidNotFindUser if request for user is unsuccessful
|
||||||
|
ErrDidNotFindUser = errors.New("did not find a user")
|
||||||
// ErrDidNotFindUser if request for user is unsuccessful
|
)
|
||||||
var ErrDidNotFindUser = errors.New("did not find a user")
|
|
||||||
|
|
||||||
// ServerStatus holds the LDAP server status
|
// ServerStatus holds the LDAP server status
|
||||||
type ServerStatus struct {
|
type ServerStatus struct {
|
||||||
|
|
@ -59,12 +54,16 @@ type IMultiLDAP interface {
|
||||||
// MultiLDAP is basic struct of LDAP authorization
|
// MultiLDAP is basic struct of LDAP authorization
|
||||||
type MultiLDAP struct {
|
type MultiLDAP struct {
|
||||||
configs []*ldap.ServerConfig
|
configs []*ldap.ServerConfig
|
||||||
|
cfg *setting.Cfg
|
||||||
|
log log.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates the new LDAP auth
|
// New creates the new LDAP auth
|
||||||
func New(configs []*ldap.ServerConfig) IMultiLDAP {
|
func New(configs []*ldap.ServerConfig, cfg *setting.Cfg) IMultiLDAP {
|
||||||
return &MultiLDAP{
|
return &MultiLDAP{
|
||||||
configs: configs,
|
configs: configs,
|
||||||
|
cfg: cfg,
|
||||||
|
log: log.New("ldap"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -81,7 +80,7 @@ func (multiples *MultiLDAP) Ping() ([]*ServerStatus, error) {
|
||||||
status.Host = config.Host
|
status.Host = config.Host
|
||||||
status.Port = config.Port
|
status.Port = config.Port
|
||||||
|
|
||||||
server := newLDAP(config)
|
server := newLDAP(config, multiples.cfg)
|
||||||
err := server.Dial()
|
err := server.Dial()
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
@ -109,7 +108,7 @@ func (multiples *MultiLDAP) Login(query *login.LoginUserQuery) (
|
||||||
ldapSilentErrors := []error{}
|
ldapSilentErrors := []error{}
|
||||||
|
|
||||||
for index, config := range multiples.configs {
|
for index, config := range multiples.configs {
|
||||||
server := newLDAP(config)
|
server := newLDAP(config, multiples.cfg)
|
||||||
|
|
||||||
if err := server.Dial(); err != nil {
|
if err := server.Dial(); err != nil {
|
||||||
logDialFailure(err, config)
|
logDialFailure(err, config)
|
||||||
|
|
@ -127,7 +126,7 @@ func (multiples *MultiLDAP) Login(query *login.LoginUserQuery) (
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if isSilentError(err) {
|
if isSilentError(err) {
|
||||||
ldapSilentErrors = append(ldapSilentErrors, err)
|
ldapSilentErrors = append(ldapSilentErrors, err)
|
||||||
logger.Debug(
|
multiples.log.Debug(
|
||||||
"unable to login with LDAP - skipping server",
|
"unable to login with LDAP - skipping server",
|
||||||
"host", config.Host,
|
"host", config.Host,
|
||||||
"port", config.Port,
|
"port", config.Port,
|
||||||
|
|
@ -167,7 +166,7 @@ func (multiples *MultiLDAP) User(login string) (
|
||||||
|
|
||||||
search := []string{login}
|
search := []string{login}
|
||||||
for index, config := range multiples.configs {
|
for index, config := range multiples.configs {
|
||||||
server := newLDAP(config)
|
server := newLDAP(config, multiples.cfg)
|
||||||
|
|
||||||
if err := server.Dial(); err != nil {
|
if err := server.Dial(); err != nil {
|
||||||
logDialFailure(err, config)
|
logDialFailure(err, config)
|
||||||
|
|
@ -210,7 +209,7 @@ func (multiples *MultiLDAP) Users(logins []string) (
|
||||||
}
|
}
|
||||||
|
|
||||||
for index, config := range multiples.configs {
|
for index, config := range multiples.configs {
|
||||||
server := newLDAP(config)
|
server := newLDAP(config, multiples.cfg)
|
||||||
|
|
||||||
if err := server.Dial(); err != nil {
|
if err := server.Dial(); err != nil {
|
||||||
logDialFailure(err, config)
|
logDialFailure(err, config)
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/services/ldap"
|
"github.com/grafana/grafana/pkg/services/ldap"
|
||||||
"github.com/grafana/grafana/pkg/services/login"
|
"github.com/grafana/grafana/pkg/services/login"
|
||||||
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
|
|
||||||
//TODO(sh0rez): remove once import cycle resolved
|
//TODO(sh0rez): remove once import cycle resolved
|
||||||
_ "github.com/grafana/grafana/pkg/api/response"
|
_ "github.com/grafana/grafana/pkg/api/response"
|
||||||
|
|
@ -18,7 +19,7 @@ func TestMultiLDAP(t *testing.T) {
|
||||||
t.Run("Should return error for absent config list", func(t *testing.T) {
|
t.Run("Should return error for absent config list", func(t *testing.T) {
|
||||||
setup()
|
setup()
|
||||||
|
|
||||||
multi := New([]*ldap.ServerConfig{})
|
multi := New([]*ldap.ServerConfig{}, setting.NewCfg())
|
||||||
_, err := multi.Ping()
|
_, err := multi.Ping()
|
||||||
|
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|
@ -34,7 +35,7 @@ func TestMultiLDAP(t *testing.T) {
|
||||||
|
|
||||||
multi := New([]*ldap.ServerConfig{
|
multi := New([]*ldap.ServerConfig{
|
||||||
{Host: "10.0.0.1", Port: 361},
|
{Host: "10.0.0.1", Port: 361},
|
||||||
})
|
}, setting.NewCfg())
|
||||||
|
|
||||||
statuses, err := multi.Ping()
|
statuses, err := multi.Ping()
|
||||||
|
|
||||||
|
|
@ -52,7 +53,7 @@ func TestMultiLDAP(t *testing.T) {
|
||||||
|
|
||||||
multi := New([]*ldap.ServerConfig{
|
multi := New([]*ldap.ServerConfig{
|
||||||
{Host: "10.0.0.1", Port: 361},
|
{Host: "10.0.0.1", Port: 361},
|
||||||
})
|
}, setting.NewCfg())
|
||||||
|
|
||||||
statuses, err := multi.Ping()
|
statuses, err := multi.Ping()
|
||||||
|
|
||||||
|
|
@ -70,7 +71,7 @@ func TestMultiLDAP(t *testing.T) {
|
||||||
t.Run("Should return error for absent config list", func(t *testing.T) {
|
t.Run("Should return error for absent config list", func(t *testing.T) {
|
||||||
setup()
|
setup()
|
||||||
|
|
||||||
multi := New([]*ldap.ServerConfig{})
|
multi := New([]*ldap.ServerConfig{}, setting.NewCfg())
|
||||||
_, err := multi.Login(&login.LoginUserQuery{})
|
_, err := multi.Login(&login.LoginUserQuery{})
|
||||||
|
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|
@ -87,7 +88,7 @@ func TestMultiLDAP(t *testing.T) {
|
||||||
|
|
||||||
multi := New([]*ldap.ServerConfig{
|
multi := New([]*ldap.ServerConfig{
|
||||||
{}, {},
|
{}, {},
|
||||||
})
|
}, setting.NewCfg())
|
||||||
|
|
||||||
_, err := multi.Login(&login.LoginUserQuery{})
|
_, err := multi.Login(&login.LoginUserQuery{})
|
||||||
|
|
||||||
|
|
@ -103,7 +104,7 @@ func TestMultiLDAP(t *testing.T) {
|
||||||
|
|
||||||
multi := New([]*ldap.ServerConfig{
|
multi := New([]*ldap.ServerConfig{
|
||||||
{}, {},
|
{}, {},
|
||||||
})
|
}, setting.NewCfg())
|
||||||
_, err := multi.Login(&login.LoginUserQuery{})
|
_, err := multi.Login(&login.LoginUserQuery{})
|
||||||
|
|
||||||
require.Equal(t, 2, mock.dialCalledTimes)
|
require.Equal(t, 2, mock.dialCalledTimes)
|
||||||
|
|
@ -124,7 +125,7 @@ func TestMultiLDAP(t *testing.T) {
|
||||||
|
|
||||||
multi := New([]*ldap.ServerConfig{
|
multi := New([]*ldap.ServerConfig{
|
||||||
{}, {},
|
{}, {},
|
||||||
})
|
}, setting.NewCfg())
|
||||||
result, err := multi.Login(&login.LoginUserQuery{})
|
result, err := multi.Login(&login.LoginUserQuery{})
|
||||||
|
|
||||||
require.Equal(t, 1, mock.dialCalledTimes)
|
require.Equal(t, 1, mock.dialCalledTimes)
|
||||||
|
|
@ -144,7 +145,7 @@ func TestMultiLDAP(t *testing.T) {
|
||||||
|
|
||||||
multi := New([]*ldap.ServerConfig{
|
multi := New([]*ldap.ServerConfig{
|
||||||
{}, {},
|
{}, {},
|
||||||
})
|
}, setting.NewCfg())
|
||||||
_, err := multi.Login(&login.LoginUserQuery{})
|
_, err := multi.Login(&login.LoginUserQuery{})
|
||||||
|
|
||||||
require.Equal(t, 2, mock.dialCalledTimes)
|
require.Equal(t, 2, mock.dialCalledTimes)
|
||||||
|
|
@ -163,7 +164,7 @@ func TestMultiLDAP(t *testing.T) {
|
||||||
|
|
||||||
multi := New([]*ldap.ServerConfig{
|
multi := New([]*ldap.ServerConfig{
|
||||||
{}, {},
|
{}, {},
|
||||||
})
|
}, setting.NewCfg())
|
||||||
_, err := multi.Login(&login.LoginUserQuery{})
|
_, err := multi.Login(&login.LoginUserQuery{})
|
||||||
|
|
||||||
require.Equal(t, 2, mock.dialCalledTimes)
|
require.Equal(t, 2, mock.dialCalledTimes)
|
||||||
|
|
@ -183,7 +184,7 @@ func TestMultiLDAP(t *testing.T) {
|
||||||
|
|
||||||
multi := New([]*ldap.ServerConfig{
|
multi := New([]*ldap.ServerConfig{
|
||||||
{}, {},
|
{}, {},
|
||||||
})
|
}, setting.NewCfg())
|
||||||
_, err := multi.Login(&login.LoginUserQuery{})
|
_, err := multi.Login(&login.LoginUserQuery{})
|
||||||
|
|
||||||
require.Equal(t, 2, mock.dialCalledTimes)
|
require.Equal(t, 2, mock.dialCalledTimes)
|
||||||
|
|
@ -201,7 +202,7 @@ func TestMultiLDAP(t *testing.T) {
|
||||||
|
|
||||||
multi := New([]*ldap.ServerConfig{
|
multi := New([]*ldap.ServerConfig{
|
||||||
{}, {},
|
{}, {},
|
||||||
})
|
}, setting.NewCfg())
|
||||||
_, err := multi.Login(&login.LoginUserQuery{})
|
_, err := multi.Login(&login.LoginUserQuery{})
|
||||||
|
|
||||||
require.Equal(t, 1, mock.dialCalledTimes)
|
require.Equal(t, 1, mock.dialCalledTimes)
|
||||||
|
|
@ -218,7 +219,7 @@ func TestMultiLDAP(t *testing.T) {
|
||||||
t.Run("Should return error for absent config list", func(t *testing.T) {
|
t.Run("Should return error for absent config list", func(t *testing.T) {
|
||||||
setup()
|
setup()
|
||||||
|
|
||||||
multi := New([]*ldap.ServerConfig{})
|
multi := New([]*ldap.ServerConfig{}, setting.NewCfg())
|
||||||
_, _, err := multi.User("test")
|
_, _, err := multi.User("test")
|
||||||
|
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|
@ -235,7 +236,7 @@ func TestMultiLDAP(t *testing.T) {
|
||||||
|
|
||||||
multi := New([]*ldap.ServerConfig{
|
multi := New([]*ldap.ServerConfig{
|
||||||
{}, {},
|
{}, {},
|
||||||
})
|
}, setting.NewCfg())
|
||||||
|
|
||||||
_, _, err := multi.User("test")
|
_, _, err := multi.User("test")
|
||||||
|
|
||||||
|
|
@ -250,7 +251,7 @@ func TestMultiLDAP(t *testing.T) {
|
||||||
|
|
||||||
multi := New([]*ldap.ServerConfig{
|
multi := New([]*ldap.ServerConfig{
|
||||||
{}, {},
|
{}, {},
|
||||||
})
|
}, setting.NewCfg())
|
||||||
_, _, err := multi.User("test")
|
_, _, err := multi.User("test")
|
||||||
|
|
||||||
require.Equal(t, 2, mock.dialCalledTimes)
|
require.Equal(t, 2, mock.dialCalledTimes)
|
||||||
|
|
@ -270,7 +271,7 @@ func TestMultiLDAP(t *testing.T) {
|
||||||
|
|
||||||
multi := New([]*ldap.ServerConfig{
|
multi := New([]*ldap.ServerConfig{
|
||||||
{}, {},
|
{}, {},
|
||||||
})
|
}, setting.NewCfg())
|
||||||
_, _, err := multi.User("test")
|
_, _, err := multi.User("test")
|
||||||
|
|
||||||
require.Equal(t, 1, mock.dialCalledTimes)
|
require.Equal(t, 1, mock.dialCalledTimes)
|
||||||
|
|
@ -297,7 +298,7 @@ func TestMultiLDAP(t *testing.T) {
|
||||||
|
|
||||||
multi := New([]*ldap.ServerConfig{
|
multi := New([]*ldap.ServerConfig{
|
||||||
{}, {},
|
{}, {},
|
||||||
})
|
}, setting.NewCfg())
|
||||||
user, _, err := multi.User("test")
|
user, _, err := multi.User("test")
|
||||||
|
|
||||||
require.Equal(t, 1, mock.dialCalledTimes)
|
require.Equal(t, 1, mock.dialCalledTimes)
|
||||||
|
|
@ -318,7 +319,7 @@ func TestMultiLDAP(t *testing.T) {
|
||||||
|
|
||||||
multi := New([]*ldap.ServerConfig{
|
multi := New([]*ldap.ServerConfig{
|
||||||
{}, {},
|
{}, {},
|
||||||
})
|
}, setting.NewCfg())
|
||||||
_, _, err := multi.User("test")
|
_, _, err := multi.User("test")
|
||||||
|
|
||||||
require.Equal(t, 2, mock.dialCalledTimes)
|
require.Equal(t, 2, mock.dialCalledTimes)
|
||||||
|
|
@ -337,7 +338,7 @@ func TestMultiLDAP(t *testing.T) {
|
||||||
|
|
||||||
multi := New([]*ldap.ServerConfig{
|
multi := New([]*ldap.ServerConfig{
|
||||||
{}, {},
|
{}, {},
|
||||||
})
|
}, setting.NewCfg())
|
||||||
_, err := multi.Users([]string{"test"})
|
_, err := multi.Users([]string{"test"})
|
||||||
|
|
||||||
require.Equal(t, 2, mock.dialCalledTimes)
|
require.Equal(t, 2, mock.dialCalledTimes)
|
||||||
|
|
@ -348,7 +349,7 @@ func TestMultiLDAP(t *testing.T) {
|
||||||
t.Run("Should return error for absent config list", func(t *testing.T) {
|
t.Run("Should return error for absent config list", func(t *testing.T) {
|
||||||
setup()
|
setup()
|
||||||
|
|
||||||
multi := New([]*ldap.ServerConfig{})
|
multi := New([]*ldap.ServerConfig{}, setting.NewCfg())
|
||||||
_, err := multi.Users([]string{"test"})
|
_, err := multi.Users([]string{"test"})
|
||||||
|
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
|
@ -365,7 +366,7 @@ func TestMultiLDAP(t *testing.T) {
|
||||||
|
|
||||||
multi := New([]*ldap.ServerConfig{
|
multi := New([]*ldap.ServerConfig{
|
||||||
{}, {},
|
{}, {},
|
||||||
})
|
}, setting.NewCfg())
|
||||||
|
|
||||||
_, err := multi.Users([]string{"test"})
|
_, err := multi.Users([]string{"test"})
|
||||||
|
|
||||||
|
|
@ -380,7 +381,7 @@ func TestMultiLDAP(t *testing.T) {
|
||||||
|
|
||||||
multi := New([]*ldap.ServerConfig{
|
multi := New([]*ldap.ServerConfig{
|
||||||
{}, {},
|
{}, {},
|
||||||
})
|
}, setting.NewCfg())
|
||||||
_, err := multi.Users([]string{"test"})
|
_, err := multi.Users([]string{"test"})
|
||||||
|
|
||||||
require.Equal(t, 2, mock.dialCalledTimes)
|
require.Equal(t, 2, mock.dialCalledTimes)
|
||||||
|
|
@ -400,7 +401,7 @@ func TestMultiLDAP(t *testing.T) {
|
||||||
|
|
||||||
multi := New([]*ldap.ServerConfig{
|
multi := New([]*ldap.ServerConfig{
|
||||||
{}, {},
|
{}, {},
|
||||||
})
|
}, setting.NewCfg())
|
||||||
_, err := multi.Users([]string{"test"})
|
_, err := multi.Users([]string{"test"})
|
||||||
|
|
||||||
require.Equal(t, 1, mock.dialCalledTimes)
|
require.Equal(t, 1, mock.dialCalledTimes)
|
||||||
|
|
@ -433,7 +434,7 @@ func TestMultiLDAP(t *testing.T) {
|
||||||
|
|
||||||
multi := New([]*ldap.ServerConfig{
|
multi := New([]*ldap.ServerConfig{
|
||||||
{}, {},
|
{}, {},
|
||||||
})
|
}, setting.NewCfg())
|
||||||
users, err := multi.Users([]string{"test"})
|
users, err := multi.Users([]string{"test"})
|
||||||
|
|
||||||
require.Equal(t, 2, mock.dialCalledTimes)
|
require.Equal(t, 2, mock.dialCalledTimes)
|
||||||
|
|
@ -511,7 +512,7 @@ func (mock *mockLDAP) Bind() error {
|
||||||
func setup() *mockLDAP {
|
func setup() *mockLDAP {
|
||||||
mock := &mockLDAP{}
|
mock := &mockLDAP{}
|
||||||
|
|
||||||
newLDAP = func(config *ldap.ServerConfig) ldap.IServer {
|
newLDAP = func(config *ldap.ServerConfig, cfg *setting.Cfg) ldap.IServer {
|
||||||
return mock
|
return mock
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/grafana/grafana/pkg/services/ldap"
|
||||||
|
"github.com/grafana/grafana/pkg/services/ldap/multildap"
|
||||||
|
"github.com/grafana/grafana/pkg/services/login"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LDAPFakeService struct {
|
||||||
|
ExpectedConfig *ldap.Config
|
||||||
|
ExpectedClient multildap.IMultiLDAP
|
||||||
|
ExpectedError error
|
||||||
|
ExpectedUser *login.ExternalUserInfo
|
||||||
|
UserCalled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLDAPFakeService() *LDAPFakeService {
|
||||||
|
return &LDAPFakeService{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LDAPFakeService) ReloadConfig() error {
|
||||||
|
return s.ExpectedError
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LDAPFakeService) Config() *ldap.Config {
|
||||||
|
return s.ExpectedConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LDAPFakeService) Client() multildap.IMultiLDAP {
|
||||||
|
return s.ExpectedClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LDAPFakeService) Login(query *login.LoginUserQuery) (*login.ExternalUserInfo, error) {
|
||||||
|
return s.ExpectedUser, s.ExpectedError
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LDAPFakeService) User(username string) (*login.ExternalUserInfo, error) {
|
||||||
|
s.UserCalled = true
|
||||||
|
return s.ExpectedUser, s.ExpectedError
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/BurntSushi/toml"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
|
||||||
|
"github.com/grafana/grafana/pkg/services/ldap"
|
||||||
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
|
)
|
||||||
|
|
||||||
|
const defaultTimeout = 10
|
||||||
|
|
||||||
|
func readConfig(configFile string) (*ldap.Config, error) {
|
||||||
|
result := &ldap.Config{}
|
||||||
|
|
||||||
|
logger.Info("LDAP enabled, reading config file", "file", configFile)
|
||||||
|
|
||||||
|
// nolint:gosec
|
||||||
|
// We can ignore the gosec G304 warning on this one because `filename` comes from grafana configuration file
|
||||||
|
fileBytes, err := os.ReadFile(configFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%v: %w", "Failed to load LDAP config file", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// interpolate full toml string (it can contain ENV variables)
|
||||||
|
stringContent, err := setting.ExpandVar(string(fileBytes))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%v: %w", "Failed to expand variables", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = toml.Decode(stringContent, result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%v: %w", "Failed to load LDAP config file", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(result.Servers) == 0 {
|
||||||
|
return nil, fmt.Errorf("LDAP enabled but no LDAP servers defined in config file")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, server := range result.Servers {
|
||||||
|
// set default org id
|
||||||
|
err = assertNotEmptyCfg(server.SearchFilter, "search_filter")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%v: %w", "Failed to validate SearchFilter section", err)
|
||||||
|
}
|
||||||
|
err = assertNotEmptyCfg(server.SearchBaseDNs, "search_base_dns")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%v: %w", "Failed to validate SearchBaseDNs section", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, groupMap := range server.Groups {
|
||||||
|
if groupMap.OrgRole == "" && groupMap.IsGrafanaAdmin == nil {
|
||||||
|
return nil, fmt.Errorf("LDAP group mapping: organization role or grafana admin status is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if groupMap.OrgId == 0 {
|
||||||
|
groupMap.OrgId = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set default timeout if unspecified
|
||||||
|
if server.Timeout == 0 {
|
||||||
|
server.Timeout = defaultTimeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertNotEmptyCfg(val interface{}, propName string) error {
|
||||||
|
switch v := val.(type) {
|
||||||
|
case string:
|
||||||
|
if v == "" {
|
||||||
|
return fmt.Errorf("LDAP config file is missing option: %q", propName)
|
||||||
|
}
|
||||||
|
case []string:
|
||||||
|
if len(v) == 0 {
|
||||||
|
return fmt.Errorf("LDAP config file is missing option: %q", propName)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
fmt.Println("unknown")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,122 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
|
"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/setting"
|
||||||
|
)
|
||||||
|
|
||||||
|
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.Config
|
||||||
|
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 *setting.Cfg
|
||||||
|
ldapCfg *ldap.Config
|
||||||
|
log log.Logger
|
||||||
|
|
||||||
|
// loadingMutex locks the reading of the config so multiple requests for reloading are sequential.
|
||||||
|
loadingMutex *sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func ProvideService(cfg *setting.Cfg) *LDAPImpl {
|
||||||
|
s := &LDAPImpl{
|
||||||
|
client: nil,
|
||||||
|
ldapCfg: nil,
|
||||||
|
cfg: cfg,
|
||||||
|
log: log.New("ldap.service"),
|
||||||
|
loadingMutex: &sync.Mutex{},
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cfg.LDAPEnabled {
|
||||||
|
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) ReloadConfig() error {
|
||||||
|
if !s.cfg.LDAPEnabled {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
s.loadingMutex.Lock()
|
||||||
|
defer s.loadingMutex.Unlock()
|
||||||
|
|
||||||
|
config, err := readConfig(s.cfg.LDAPConfigFilePath)
|
||||||
|
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.Config {
|
||||||
|
return s.ldapCfg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LDAPImpl) Login(query *login.LoginUserQuery) (*login.ExternalUserInfo, error) {
|
||||||
|
if !s.cfg.LDAPEnabled {
|
||||||
|
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.LDAPEnabled {
|
||||||
|
return nil, ErrLDAPNotEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
client := s.Client()
|
||||||
|
if client == nil {
|
||||||
|
return nil, ErrUnableToCreateLDAPClient
|
||||||
|
}
|
||||||
|
|
||||||
|
user, _, err := client.User(username)
|
||||||
|
return user, err
|
||||||
|
}
|
||||||
|
|
@ -71,29 +71,6 @@ var logger = log.New("ldap")
|
||||||
// loadingMutex locks the reading of the config so multiple requests for reloading are sequential.
|
// loadingMutex locks the reading of the config so multiple requests for reloading are sequential.
|
||||||
var loadingMutex = &sync.Mutex{}
|
var loadingMutex = &sync.Mutex{}
|
||||||
|
|
||||||
// IsEnabled checks if ldap is enabled
|
|
||||||
func IsEnabled() bool {
|
|
||||||
return setting.LDAPEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
func SkipOrgRoleSync() bool {
|
|
||||||
return setting.LDAPSkipOrgRoleSync
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReloadConfig reads the config from the disk and caches it.
|
|
||||||
func ReloadConfig(ldapConfigFilePath string) error {
|
|
||||||
if !IsEnabled() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
loadingMutex.Lock()
|
|
||||||
defer loadingMutex.Unlock()
|
|
||||||
|
|
||||||
var err error
|
|
||||||
config, err = readConfig(ldapConfigFilePath)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// We need to define in this space so `GetConfig` fn
|
// We need to define in this space so `GetConfig` fn
|
||||||
// could be defined as singleton
|
// could be defined as singleton
|
||||||
var config *Config
|
var config *Config
|
||||||
|
|
@ -105,7 +82,7 @@ func GetConfig(cfg *setting.Cfg) (*Config, error) {
|
||||||
if !cfg.LDAPEnabled {
|
if !cfg.LDAPEnabled {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
} else if !IsEnabled() {
|
} else if !cfg.LDAPEnabled {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -140,14 +140,6 @@ var (
|
||||||
RudderstackSdkUrl string
|
RudderstackSdkUrl string
|
||||||
RudderstackConfigUrl string
|
RudderstackConfigUrl string
|
||||||
|
|
||||||
// LDAP
|
|
||||||
LDAPEnabled bool
|
|
||||||
LDAPSkipOrgRoleSync bool
|
|
||||||
LDAPConfigFile string
|
|
||||||
LDAPSyncCron string
|
|
||||||
LDAPAllowSignup bool
|
|
||||||
LDAPActiveSyncEnabled bool
|
|
||||||
|
|
||||||
// Alerting
|
// Alerting
|
||||||
AlertingEnabled *bool
|
AlertingEnabled *bool
|
||||||
ExecuteAlerts bool
|
ExecuteAlerts bool
|
||||||
|
|
@ -440,10 +432,12 @@ type Cfg struct {
|
||||||
GenericOAuthSkipOrgRoleSync bool
|
GenericOAuthSkipOrgRoleSync bool
|
||||||
|
|
||||||
// LDAP
|
// LDAP
|
||||||
LDAPEnabled bool
|
LDAPEnabled bool
|
||||||
LDAPSkipOrgRoleSync bool
|
LDAPSkipOrgRoleSync bool
|
||||||
LDAPConfigFilePath string
|
LDAPConfigFilePath string
|
||||||
LDAPAllowSignup bool
|
LDAPAllowSignup bool
|
||||||
|
LDAPActiveSyncEnabled bool
|
||||||
|
LDAPSyncCron string
|
||||||
|
|
||||||
DefaultTheme string
|
DefaultTheme string
|
||||||
DefaultLanguage string
|
DefaultLanguage string
|
||||||
|
|
@ -1206,16 +1200,12 @@ func (cfg *Cfg) readSAMLConfig() {
|
||||||
|
|
||||||
func (cfg *Cfg) readLDAPConfig() {
|
func (cfg *Cfg) readLDAPConfig() {
|
||||||
ldapSec := cfg.Raw.Section("auth.ldap")
|
ldapSec := cfg.Raw.Section("auth.ldap")
|
||||||
LDAPConfigFile = ldapSec.Key("config_file").String()
|
cfg.LDAPConfigFilePath = ldapSec.Key("config_file").String()
|
||||||
cfg.LDAPConfigFilePath = LDAPConfigFile
|
cfg.LDAPSyncCron = ldapSec.Key("sync_cron").String()
|
||||||
LDAPSyncCron = ldapSec.Key("sync_cron").String()
|
cfg.LDAPEnabled = ldapSec.Key("enabled").MustBool(false)
|
||||||
LDAPEnabled = ldapSec.Key("enabled").MustBool(false)
|
cfg.LDAPSkipOrgRoleSync = ldapSec.Key("skip_org_role_sync").MustBool(false)
|
||||||
cfg.LDAPEnabled = LDAPEnabled
|
cfg.LDAPActiveSyncEnabled = ldapSec.Key("active_sync_enabled").MustBool(false)
|
||||||
LDAPSkipOrgRoleSync = ldapSec.Key("skip_org_role_sync").MustBool(false)
|
cfg.LDAPAllowSignup = ldapSec.Key("allow_sign_up").MustBool(true)
|
||||||
cfg.LDAPSkipOrgRoleSync = LDAPSkipOrgRoleSync
|
|
||||||
LDAPActiveSyncEnabled = ldapSec.Key("active_sync_enabled").MustBool(false)
|
|
||||||
LDAPAllowSignup = ldapSec.Key("allow_sign_up").MustBool(true)
|
|
||||||
cfg.LDAPAllowSignup = LDAPAllowSignup
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cfg *Cfg) handleAWSConfig() {
|
func (cfg *Cfg) handleAWSConfig() {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue