mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Auth: Remove unused Authenticator service (#73143)
Auth: remove unused Authenticator service
This commit is contained in:
parent
d29f4a8f76
commit
43aab615c3
@ -12,7 +12,6 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/api/response"
|
"github.com/grafana/grafana/pkg/api/response"
|
||||||
"github.com/grafana/grafana/pkg/infra/metrics"
|
"github.com/grafana/grafana/pkg/infra/metrics"
|
||||||
"github.com/grafana/grafana/pkg/infra/network"
|
"github.com/grafana/grafana/pkg/infra/network"
|
||||||
"github.com/grafana/grafana/pkg/login"
|
|
||||||
"github.com/grafana/grafana/pkg/middleware/cookies"
|
"github.com/grafana/grafana/pkg/middleware/cookies"
|
||||||
"github.com/grafana/grafana/pkg/services/auth"
|
"github.com/grafana/grafana/pkg/services/auth"
|
||||||
"github.com/grafana/grafana/pkg/services/authn"
|
"github.com/grafana/grafana/pkg/services/authn"
|
||||||
@ -38,31 +37,39 @@ var getViewIndex = func() string {
|
|||||||
return viewIndex
|
return viewIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
errAbsoluteRedirectTo = errors.New("absolute URLs are not allowed for redirect_to cookie value")
|
||||||
|
errInvalidRedirectTo = errors.New("invalid redirect_to cookie value")
|
||||||
|
errForbiddenRedirectTo = errors.New("forbidden redirect_to cookie value")
|
||||||
|
)
|
||||||
|
|
||||||
func (hs *HTTPServer) ValidateRedirectTo(redirectTo string) error {
|
func (hs *HTTPServer) ValidateRedirectTo(redirectTo string) error {
|
||||||
to, err := url.Parse(redirectTo)
|
to, err := url.Parse(redirectTo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return login.ErrInvalidRedirectTo
|
return errInvalidRedirectTo
|
||||||
}
|
}
|
||||||
|
|
||||||
if to.IsAbs() {
|
if to.IsAbs() {
|
||||||
return login.ErrAbsoluteRedirectTo
|
return errAbsoluteRedirectTo
|
||||||
}
|
}
|
||||||
|
|
||||||
if to.Host != "" {
|
if to.Host != "" {
|
||||||
return login.ErrForbiddenRedirectTo
|
return errForbiddenRedirectTo
|
||||||
}
|
}
|
||||||
|
|
||||||
// path should have exactly one leading slash
|
// path should have exactly one leading slash
|
||||||
if !strings.HasPrefix(to.Path, "/") {
|
if !strings.HasPrefix(to.Path, "/") {
|
||||||
return login.ErrForbiddenRedirectTo
|
return errForbiddenRedirectTo
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(to.Path, "//") {
|
if strings.HasPrefix(to.Path, "//") {
|
||||||
return login.ErrForbiddenRedirectTo
|
return errForbiddenRedirectTo
|
||||||
}
|
}
|
||||||
|
|
||||||
// when using a subUrl, the redirect_to should start with the subUrl (which contains the leading slash), otherwise the redirect
|
// when using a subUrl, the redirect_to should start with the subUrl (which contains the leading slash), otherwise the redirect
|
||||||
// will send the user to the wrong location
|
// will send the user to the wrong location
|
||||||
if hs.Cfg.AppSubURL != "" && !strings.HasPrefix(to.Path, hs.Cfg.AppSubURL+"/") {
|
if hs.Cfg.AppSubURL != "" && !strings.HasPrefix(to.Path, hs.Cfg.AppSubURL+"/") {
|
||||||
return login.ErrInvalidRedirectTo
|
return errInvalidRedirectTo
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/metrics"
|
"github.com/grafana/grafana/pkg/infra/metrics"
|
||||||
"github.com/grafana/grafana/pkg/login"
|
|
||||||
"github.com/grafana/grafana/pkg/middleware/cookies"
|
"github.com/grafana/grafana/pkg/middleware/cookies"
|
||||||
"github.com/grafana/grafana/pkg/services/authn"
|
"github.com/grafana/grafana/pkg/services/authn"
|
||||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||||
@ -21,7 +22,7 @@ func (hs *HTTPServer) OAuthLogin(reqCtx *contextmodel.ReqContext) {
|
|||||||
errorDesc := reqCtx.Query("error_description")
|
errorDesc := reqCtx.Query("error_description")
|
||||||
hs.log.Error("failed to login ", "error", errorParam, "errorDesc", errorDesc)
|
hs.log.Error("failed to login ", "error", errorParam, "errorDesc", errorDesc)
|
||||||
|
|
||||||
hs.redirectWithError(reqCtx, login.ErrProviderDeniedRequest, "error", errorParam, "errorDesc", errorDesc)
|
hs.redirectWithError(reqCtx, errors.New("login provider denied login request"), "error", errorParam, "errorDesc", errorDesc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,114 +0,0 @@
|
|||||||
package login
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/db"
|
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
|
||||||
"github.com/grafana/grafana/pkg/services/ldap"
|
|
||||||
"github.com/grafana/grafana/pkg/services/login"
|
|
||||||
"github.com/grafana/grafana/pkg/services/loginattempt"
|
|
||||||
"github.com/grafana/grafana/pkg/services/user"
|
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrEmailNotAllowed = errors.New("required email domain not fulfilled")
|
|
||||||
ErrInvalidCredentials = errors.New("invalid username or password")
|
|
||||||
ErrNoEmail = errors.New("login provider didn't return an email address")
|
|
||||||
ErrProviderDeniedRequest = errors.New("login provider denied login request")
|
|
||||||
ErrTooManyLoginAttempts = errors.New("too many consecutive incorrect login attempts for user - login for user temporarily blocked")
|
|
||||||
ErrPasswordEmpty = errors.New("no password provided")
|
|
||||||
ErrUserDisabled = errors.New("user is disabled")
|
|
||||||
ErrAbsoluteRedirectTo = errors.New("absolute URLs are not allowed for redirect_to cookie value")
|
|
||||||
ErrInvalidRedirectTo = errors.New("invalid redirect_to cookie value")
|
|
||||||
ErrForbiddenRedirectTo = errors.New("forbidden redirect_to cookie value")
|
|
||||||
ErrNoAuthProvider = errors.New("enable at least one login provider")
|
|
||||||
)
|
|
||||||
|
|
||||||
var loginLogger = log.New("login")
|
|
||||||
|
|
||||||
type Authenticator interface {
|
|
||||||
AuthenticateUser(context.Context, *login.LoginUserQuery) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type AuthenticatorService struct {
|
|
||||||
loginService login.Service
|
|
||||||
loginAttemptService loginattempt.Service
|
|
||||||
userService user.Service
|
|
||||||
cfg *setting.Cfg
|
|
||||||
}
|
|
||||||
|
|
||||||
func ProvideService(store db.DB, loginService login.Service,
|
|
||||||
loginAttemptService loginattempt.Service,
|
|
||||||
userService user.Service, cfg *setting.Cfg) *AuthenticatorService {
|
|
||||||
a := &AuthenticatorService{
|
|
||||||
loginService: loginService,
|
|
||||||
loginAttemptService: loginAttemptService,
|
|
||||||
userService: userService,
|
|
||||||
cfg: cfg,
|
|
||||||
}
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
|
|
||||||
// AuthenticateUser authenticates the user via username & password
|
|
||||||
func (a *AuthenticatorService) AuthenticateUser(ctx context.Context, query *login.LoginUserQuery) error {
|
|
||||||
ok, err := a.loginAttemptService.Validate(ctx, query.Username)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
return ErrTooManyLoginAttempts
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := validatePasswordSet(query.Password); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
isGrafanaLoginEnabled := !query.Cfg.DisableLogin
|
|
||||||
|
|
||||||
if isGrafanaLoginEnabled {
|
|
||||||
err = loginUsingGrafanaDB(ctx, query, a.userService)
|
|
||||||
}
|
|
||||||
|
|
||||||
if isGrafanaLoginEnabled && (err == nil || (!errors.Is(err, user.ErrUserNotFound) && !errors.Is(err, ErrInvalidCredentials) &&
|
|
||||||
!errors.Is(err, ErrUserDisabled))) {
|
|
||||||
query.AuthModule = "grafana"
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ldapEnabled, ldapErr := loginUsingLDAP(ctx, query, a.loginService, a.cfg)
|
|
||||||
if ldapEnabled {
|
|
||||||
query.AuthModule = login.LDAPAuthModule
|
|
||||||
if ldapErr == nil || !errors.Is(ldapErr, ldap.ErrInvalidCredentials) {
|
|
||||||
return ldapErr
|
|
||||||
}
|
|
||||||
|
|
||||||
if !errors.Is(err, ErrUserDisabled) || !errors.Is(ldapErr, ldap.ErrInvalidCredentials) {
|
|
||||||
err = ldapErr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if errors.Is(err, ErrInvalidCredentials) || errors.Is(err, ldap.ErrInvalidCredentials) {
|
|
||||||
if err := a.loginAttemptService.Add(ctx, query.Username, query.IpAddress); err != nil {
|
|
||||||
loginLogger.Error("Failed to save invalid login attempt", "err", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ErrInvalidCredentials
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isGrafanaLoginEnabled && !ldapEnabled {
|
|
||||||
return ErrNoAuthProvider
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func validatePasswordSet(password string) error {
|
|
||||||
if len(password) == 0 {
|
|
||||||
return ErrPasswordEmpty
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,241 +0,0 @@
|
|||||||
package login
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/services/ldap"
|
|
||||||
"github.com/grafana/grafana/pkg/services/login"
|
|
||||||
"github.com/grafana/grafana/pkg/services/login/logintest"
|
|
||||||
"github.com/grafana/grafana/pkg/services/loginattempt/loginattempttest"
|
|
||||||
"github.com/grafana/grafana/pkg/services/user"
|
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestAuthenticateUser(t *testing.T) {
|
|
||||||
authScenario(t, "When a user authenticates without setting a password", func(sc *authScenarioContext) {
|
|
||||||
mockLoginUsingGrafanaDB(nil, sc)
|
|
||||||
mockLoginUsingLDAP(false, nil, sc)
|
|
||||||
|
|
||||||
loginAttemptService := &loginattempttest.FakeLoginAttemptService{ExpectedValid: true}
|
|
||||||
cfg := setting.NewCfg()
|
|
||||||
a := AuthenticatorService{loginAttemptService: loginAttemptService, loginService: &logintest.LoginServiceFake{}, cfg: cfg}
|
|
||||||
err := a.AuthenticateUser(context.Background(), &login.LoginUserQuery{
|
|
||||||
Username: "user",
|
|
||||||
Password: "",
|
|
||||||
})
|
|
||||||
|
|
||||||
require.EqualError(t, err, ErrPasswordEmpty.Error())
|
|
||||||
assert.False(t, sc.grafanaLoginWasCalled)
|
|
||||||
assert.False(t, sc.ldapLoginWasCalled)
|
|
||||||
assert.Empty(t, sc.loginUserQuery.AuthModule)
|
|
||||||
})
|
|
||||||
|
|
||||||
authScenario(t, "When user authenticates with no auth provider enabled", func(sc *authScenarioContext) {
|
|
||||||
cfg := setting.NewCfg()
|
|
||||||
sc.loginUserQuery.Cfg.DisableLogin = true
|
|
||||||
|
|
||||||
loginAttemptService := &loginattempttest.MockLoginAttemptService{ExpectedValid: true}
|
|
||||||
a := AuthenticatorService{loginAttemptService: loginAttemptService, loginService: &logintest.LoginServiceFake{}, cfg: cfg}
|
|
||||||
err := a.AuthenticateUser(context.Background(), sc.loginUserQuery)
|
|
||||||
|
|
||||||
require.EqualError(t, err, ErrNoAuthProvider.Error())
|
|
||||||
assert.False(t, sc.grafanaLoginWasCalled)
|
|
||||||
assert.False(t, sc.ldapLoginWasCalled)
|
|
||||||
assert.Equal(t, "", sc.loginUserQuery.AuthModule)
|
|
||||||
assert.False(t, loginAttemptService.AddCalled)
|
|
||||||
assert.True(t, loginAttemptService.ValidateCalled)
|
|
||||||
})
|
|
||||||
|
|
||||||
authScenario(t, "When a user authenticates having too many login attempts", func(sc *authScenarioContext) {
|
|
||||||
cfg := setting.NewCfg()
|
|
||||||
mockLoginUsingGrafanaDB(nil, sc)
|
|
||||||
mockLoginUsingLDAP(true, nil, sc)
|
|
||||||
|
|
||||||
loginAttemptService := &loginattempttest.MockLoginAttemptService{ExpectedValid: false}
|
|
||||||
a := AuthenticatorService{loginAttemptService: loginAttemptService, loginService: &logintest.LoginServiceFake{}, cfg: cfg}
|
|
||||||
err := a.AuthenticateUser(context.Background(), sc.loginUserQuery)
|
|
||||||
|
|
||||||
require.EqualError(t, err, ErrTooManyLoginAttempts.Error())
|
|
||||||
assert.False(t, sc.grafanaLoginWasCalled)
|
|
||||||
assert.False(t, sc.ldapLoginWasCalled)
|
|
||||||
assert.Empty(t, sc.loginUserQuery.AuthModule)
|
|
||||||
assert.False(t, loginAttemptService.AddCalled)
|
|
||||||
assert.True(t, loginAttemptService.ValidateCalled)
|
|
||||||
})
|
|
||||||
|
|
||||||
authScenario(t, "When grafana user authenticate with valid credentials", func(sc *authScenarioContext) {
|
|
||||||
cfg := setting.NewCfg()
|
|
||||||
mockLoginUsingGrafanaDB(nil, sc)
|
|
||||||
mockLoginUsingLDAP(true, ErrInvalidCredentials, sc)
|
|
||||||
|
|
||||||
loginAttemptService := &loginattempttest.MockLoginAttemptService{ExpectedValid: true}
|
|
||||||
a := AuthenticatorService{loginAttemptService: loginAttemptService, loginService: &logintest.LoginServiceFake{}, cfg: cfg}
|
|
||||||
err := a.AuthenticateUser(context.Background(), sc.loginUserQuery)
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.True(t, sc.grafanaLoginWasCalled)
|
|
||||||
assert.False(t, sc.ldapLoginWasCalled)
|
|
||||||
assert.Equal(t, "grafana", sc.loginUserQuery.AuthModule)
|
|
||||||
assert.False(t, loginAttemptService.AddCalled)
|
|
||||||
assert.True(t, loginAttemptService.ValidateCalled)
|
|
||||||
})
|
|
||||||
|
|
||||||
authScenario(t, "When grafana user authenticate and unexpected error occurs", func(sc *authScenarioContext) {
|
|
||||||
cfg := setting.NewCfg()
|
|
||||||
customErr := errors.New("custom")
|
|
||||||
mockLoginUsingGrafanaDB(customErr, sc)
|
|
||||||
mockLoginUsingLDAP(true, ErrInvalidCredentials, sc)
|
|
||||||
|
|
||||||
loginAttemptService := &loginattempttest.MockLoginAttemptService{ExpectedValid: true}
|
|
||||||
a := AuthenticatorService{loginAttemptService: loginAttemptService, loginService: &logintest.LoginServiceFake{}, cfg: cfg}
|
|
||||||
err := a.AuthenticateUser(context.Background(), sc.loginUserQuery)
|
|
||||||
|
|
||||||
require.EqualError(t, err, customErr.Error())
|
|
||||||
assert.True(t, sc.grafanaLoginWasCalled)
|
|
||||||
assert.False(t, sc.ldapLoginWasCalled)
|
|
||||||
assert.Equal(t, "grafana", sc.loginUserQuery.AuthModule)
|
|
||||||
assert.False(t, loginAttemptService.AddCalled)
|
|
||||||
assert.True(t, loginAttemptService.ValidateCalled)
|
|
||||||
})
|
|
||||||
|
|
||||||
authScenario(t, "When a non-existing grafana user authenticate and ldap disabled", func(sc *authScenarioContext) {
|
|
||||||
cfg := setting.NewCfg()
|
|
||||||
mockLoginUsingGrafanaDB(user.ErrUserNotFound, sc)
|
|
||||||
mockLoginUsingLDAP(false, nil, sc)
|
|
||||||
|
|
||||||
loginAttemptService := &loginattempttest.MockLoginAttemptService{ExpectedValid: true}
|
|
||||||
a := AuthenticatorService{loginAttemptService: loginAttemptService, loginService: &logintest.LoginServiceFake{}, cfg: cfg}
|
|
||||||
err := a.AuthenticateUser(context.Background(), sc.loginUserQuery)
|
|
||||||
|
|
||||||
require.EqualError(t, err, user.ErrUserNotFound.Error())
|
|
||||||
assert.True(t, sc.grafanaLoginWasCalled)
|
|
||||||
assert.True(t, sc.ldapLoginWasCalled)
|
|
||||||
assert.Empty(t, sc.loginUserQuery.AuthModule)
|
|
||||||
assert.False(t, loginAttemptService.AddCalled)
|
|
||||||
assert.True(t, loginAttemptService.ValidateCalled)
|
|
||||||
})
|
|
||||||
|
|
||||||
authScenario(t, "When a non-existing grafana user authenticate and invalid ldap credentials", func(sc *authScenarioContext) {
|
|
||||||
cfg := setting.NewCfg()
|
|
||||||
cfg.LDAPAuthEnabled = true
|
|
||||||
mockLoginUsingGrafanaDB(user.ErrUserNotFound, sc)
|
|
||||||
mockLoginUsingLDAP(true, ldap.ErrInvalidCredentials, sc)
|
|
||||||
|
|
||||||
loginAttemptService := &loginattempttest.MockLoginAttemptService{ExpectedValid: true}
|
|
||||||
a := AuthenticatorService{loginAttemptService: loginAttemptService, loginService: &logintest.LoginServiceFake{}, cfg: cfg}
|
|
||||||
err := a.AuthenticateUser(context.Background(), sc.loginUserQuery)
|
|
||||||
|
|
||||||
require.EqualError(t, err, ErrInvalidCredentials.Error())
|
|
||||||
assert.True(t, sc.grafanaLoginWasCalled)
|
|
||||||
assert.True(t, sc.ldapLoginWasCalled)
|
|
||||||
assert.Equal(t, login.LDAPAuthModule, sc.loginUserQuery.AuthModule)
|
|
||||||
assert.True(t, loginAttemptService.AddCalled)
|
|
||||||
assert.True(t, loginAttemptService.ValidateCalled)
|
|
||||||
})
|
|
||||||
|
|
||||||
authScenario(t, "When a non-existing grafana user authenticate and valid ldap credentials", func(sc *authScenarioContext) {
|
|
||||||
cfg := setting.NewCfg()
|
|
||||||
cfg.LDAPAuthEnabled = true
|
|
||||||
mockLoginUsingGrafanaDB(user.ErrUserNotFound, sc)
|
|
||||||
mockLoginUsingLDAP(true, nil, sc)
|
|
||||||
|
|
||||||
loginAttemptService := &loginattempttest.MockLoginAttemptService{ExpectedValid: true}
|
|
||||||
a := AuthenticatorService{loginAttemptService: loginAttemptService, loginService: &logintest.LoginServiceFake{}, cfg: cfg}
|
|
||||||
err := a.AuthenticateUser(context.Background(), sc.loginUserQuery)
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
assert.True(t, sc.grafanaLoginWasCalled)
|
|
||||||
assert.True(t, sc.ldapLoginWasCalled)
|
|
||||||
assert.Equal(t, login.LDAPAuthModule, sc.loginUserQuery.AuthModule)
|
|
||||||
assert.False(t, loginAttemptService.AddCalled)
|
|
||||||
assert.True(t, loginAttemptService.ValidateCalled)
|
|
||||||
})
|
|
||||||
|
|
||||||
authScenario(t, "When a non-existing grafana user authenticate and ldap returns unexpected error", func(sc *authScenarioContext) {
|
|
||||||
cfg := setting.NewCfg()
|
|
||||||
cfg.LDAPAuthEnabled = true
|
|
||||||
customErr := errors.New("custom")
|
|
||||||
mockLoginUsingGrafanaDB(user.ErrUserNotFound, sc)
|
|
||||||
mockLoginUsingLDAP(true, customErr, sc)
|
|
||||||
|
|
||||||
loginAttemptService := &loginattempttest.MockLoginAttemptService{ExpectedValid: true}
|
|
||||||
a := AuthenticatorService{loginAttemptService: loginAttemptService, loginService: &logintest.LoginServiceFake{}, cfg: cfg}
|
|
||||||
err := a.AuthenticateUser(context.Background(), sc.loginUserQuery)
|
|
||||||
|
|
||||||
require.EqualError(t, err, customErr.Error())
|
|
||||||
assert.True(t, sc.grafanaLoginWasCalled)
|
|
||||||
assert.True(t, sc.ldapLoginWasCalled)
|
|
||||||
assert.Equal(t, login.LDAPAuthModule, sc.loginUserQuery.AuthModule)
|
|
||||||
assert.False(t, loginAttemptService.AddCalled)
|
|
||||||
assert.True(t, loginAttemptService.ValidateCalled)
|
|
||||||
})
|
|
||||||
|
|
||||||
authScenario(t, "When grafana user authenticate with invalid credentials and invalid ldap credentials", func(sc *authScenarioContext) {
|
|
||||||
cfg := setting.NewCfg()
|
|
||||||
cfg.LDAPAuthEnabled = true
|
|
||||||
mockLoginUsingGrafanaDB(ErrInvalidCredentials, sc)
|
|
||||||
mockLoginUsingLDAP(true, ldap.ErrInvalidCredentials, sc)
|
|
||||||
|
|
||||||
loginAttemptService := &loginattempttest.MockLoginAttemptService{ExpectedValid: true}
|
|
||||||
a := AuthenticatorService{loginAttemptService: loginAttemptService, loginService: &logintest.LoginServiceFake{}, cfg: cfg}
|
|
||||||
err := a.AuthenticateUser(context.Background(), sc.loginUserQuery)
|
|
||||||
|
|
||||||
require.EqualError(t, err, ErrInvalidCredentials.Error())
|
|
||||||
assert.True(t, sc.grafanaLoginWasCalled)
|
|
||||||
assert.True(t, sc.ldapLoginWasCalled)
|
|
||||||
assert.True(t, loginAttemptService.AddCalled)
|
|
||||||
assert.True(t, loginAttemptService.ValidateCalled)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
type authScenarioContext struct {
|
|
||||||
loginUserQuery *login.LoginUserQuery
|
|
||||||
grafanaLoginWasCalled bool
|
|
||||||
ldapLoginWasCalled bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type authScenarioFunc func(sc *authScenarioContext)
|
|
||||||
|
|
||||||
func mockLoginUsingGrafanaDB(err error, sc *authScenarioContext) {
|
|
||||||
loginUsingGrafanaDB = func(ctx context.Context, query *login.LoginUserQuery, _ user.Service) error {
|
|
||||||
sc.grafanaLoginWasCalled = true
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func mockLoginUsingLDAP(enabled bool, err error, sc *authScenarioContext) {
|
|
||||||
loginUsingLDAP = func(ctx context.Context, query *login.LoginUserQuery, _ login.Service, _ *setting.Cfg) (bool, error) {
|
|
||||||
sc.ldapLoginWasCalled = true
|
|
||||||
return enabled, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func authScenario(t *testing.T, desc string, fn authScenarioFunc) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
t.Run(desc, func(t *testing.T) {
|
|
||||||
origLoginUsingGrafanaDB := loginUsingGrafanaDB
|
|
||||||
origLoginUsingLDAP := loginUsingLDAP
|
|
||||||
cfg := setting.Cfg{DisableLogin: false}
|
|
||||||
sc := &authScenarioContext{
|
|
||||||
loginUserQuery: &login.LoginUserQuery{
|
|
||||||
Username: "user",
|
|
||||||
Password: "pwd",
|
|
||||||
IpAddress: "192.168.1.1:56433",
|
|
||||||
Cfg: &cfg,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Cleanup(func() {
|
|
||||||
loginUsingGrafanaDB = origLoginUsingGrafanaDB
|
|
||||||
loginUsingLDAP = origLoginUsingLDAP
|
|
||||||
})
|
|
||||||
|
|
||||||
fn(sc)
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
package login
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/subtle"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/services/login"
|
|
||||||
"github.com/grafana/grafana/pkg/services/user"
|
|
||||||
"github.com/grafana/grafana/pkg/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
var validatePassword = func(providedPassword string, userPassword string, userSalt string) error {
|
|
||||||
passwordHashed, err := util.EncodePassword(providedPassword, userSalt)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if subtle.ConstantTimeCompare([]byte(passwordHashed), []byte(userPassword)) != 1 {
|
|
||||||
return ErrInvalidCredentials
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var loginUsingGrafanaDB = func(ctx context.Context, query *login.LoginUserQuery, userService user.Service) error {
|
|
||||||
userQuery := user.GetUserByLoginQuery{LoginOrEmail: query.Username}
|
|
||||||
|
|
||||||
user, err := userService.GetByLogin(ctx, &userQuery)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if user.IsDisabled {
|
|
||||||
return ErrUserDisabled
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := validatePassword(query.Password, user.Password, user.Salt); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
query.User = user
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,138 +0,0 @@
|
|||||||
package login
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/db"
|
|
||||||
"github.com/grafana/grafana/pkg/infra/db/dbtest"
|
|
||||||
"github.com/grafana/grafana/pkg/services/login"
|
|
||||||
"github.com/grafana/grafana/pkg/services/user"
|
|
||||||
"github.com/grafana/grafana/pkg/services/user/usertest"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestLoginUsingGrafanaDB(t *testing.T) {
|
|
||||||
grafanaLoginScenario(t, "When login with non-existing user", func(sc *grafanaLoginScenarioContext) {
|
|
||||||
sc.withNonExistingUser()
|
|
||||||
err := loginUsingGrafanaDB(context.Background(), sc.loginUserQuery, sc.userService)
|
|
||||||
require.EqualError(t, err, user.ErrUserNotFound.Error())
|
|
||||||
|
|
||||||
assert.False(t, sc.validatePasswordCalled)
|
|
||||||
assert.Nil(t, sc.loginUserQuery.User)
|
|
||||||
})
|
|
||||||
|
|
||||||
grafanaLoginScenario(t, "When login with invalid credentials", func(sc *grafanaLoginScenarioContext) {
|
|
||||||
sc.withInvalidPassword()
|
|
||||||
err := loginUsingGrafanaDB(context.Background(), sc.loginUserQuery, sc.userService)
|
|
||||||
|
|
||||||
require.EqualError(t, err, ErrInvalidCredentials.Error())
|
|
||||||
|
|
||||||
assert.True(t, sc.validatePasswordCalled)
|
|
||||||
assert.Nil(t, sc.loginUserQuery.User)
|
|
||||||
})
|
|
||||||
|
|
||||||
grafanaLoginScenario(t, "When login with valid credentials", func(sc *grafanaLoginScenarioContext) {
|
|
||||||
sc.withValidCredentials()
|
|
||||||
err := loginUsingGrafanaDB(context.Background(), sc.loginUserQuery, sc.userService)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.True(t, sc.validatePasswordCalled)
|
|
||||||
|
|
||||||
require.NotNil(t, sc.loginUserQuery.User)
|
|
||||||
assert.Equal(t, sc.loginUserQuery.Username, sc.loginUserQuery.User.Login)
|
|
||||||
assert.Equal(t, sc.loginUserQuery.Password, sc.loginUserQuery.User.Password)
|
|
||||||
})
|
|
||||||
|
|
||||||
grafanaLoginScenario(t, "When login with disabled user", func(sc *grafanaLoginScenarioContext) {
|
|
||||||
sc.withDisabledUser()
|
|
||||||
err := loginUsingGrafanaDB(context.Background(), sc.loginUserQuery, sc.userService)
|
|
||||||
require.EqualError(t, err, ErrUserDisabled.Error())
|
|
||||||
|
|
||||||
assert.False(t, sc.validatePasswordCalled)
|
|
||||||
assert.Nil(t, sc.loginUserQuery.User)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
type grafanaLoginScenarioContext struct {
|
|
||||||
store db.DB
|
|
||||||
userService *usertest.FakeUserService
|
|
||||||
loginUserQuery *login.LoginUserQuery
|
|
||||||
validatePasswordCalled bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type grafanaLoginScenarioFunc func(c *grafanaLoginScenarioContext)
|
|
||||||
|
|
||||||
func grafanaLoginScenario(t *testing.T, desc string, fn grafanaLoginScenarioFunc) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
t.Run(desc, func(t *testing.T) {
|
|
||||||
origValidatePassword := validatePassword
|
|
||||||
|
|
||||||
sc := &grafanaLoginScenarioContext{
|
|
||||||
store: dbtest.NewFakeDB(),
|
|
||||||
loginUserQuery: &login.LoginUserQuery{
|
|
||||||
Username: "user",
|
|
||||||
Password: "pwd",
|
|
||||||
IpAddress: "192.168.1.1:56433",
|
|
||||||
},
|
|
||||||
validatePasswordCalled: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Cleanup(func() {
|
|
||||||
validatePassword = origValidatePassword
|
|
||||||
})
|
|
||||||
|
|
||||||
fn(sc)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func mockPasswordValidation(valid bool, sc *grafanaLoginScenarioContext) {
|
|
||||||
validatePassword = func(providedPassword string, userPassword string, userSalt string) error {
|
|
||||||
sc.validatePasswordCalled = true
|
|
||||||
|
|
||||||
if !valid {
|
|
||||||
return ErrInvalidCredentials
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sc *grafanaLoginScenarioContext) getUserByLoginQueryReturns(usr *user.User) {
|
|
||||||
sc.userService = usertest.NewUserServiceFake()
|
|
||||||
sc.userService.ExpectedUser = usr
|
|
||||||
if usr == nil {
|
|
||||||
sc.userService.ExpectedError = user.ErrUserNotFound
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sc *grafanaLoginScenarioContext) withValidCredentials() {
|
|
||||||
sc.getUserByLoginQueryReturns(&user.User{
|
|
||||||
ID: 1,
|
|
||||||
Login: sc.loginUserQuery.Username,
|
|
||||||
Password: sc.loginUserQuery.Password,
|
|
||||||
Salt: "salt",
|
|
||||||
})
|
|
||||||
mockPasswordValidation(true, sc)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sc *grafanaLoginScenarioContext) withNonExistingUser() {
|
|
||||||
sc.getUserByLoginQueryReturns(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sc *grafanaLoginScenarioContext) withInvalidPassword() {
|
|
||||||
sc.getUserByLoginQueryReturns(&user.User{
|
|
||||||
Password: sc.loginUserQuery.Password,
|
|
||||||
Salt: "salt",
|
|
||||||
})
|
|
||||||
mockPasswordValidation(false, sc)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sc *grafanaLoginScenarioContext) withDisabledUser() {
|
|
||||||
sc.getUserByLoginQueryReturns(&user.User{
|
|
||||||
IsDisabled: true,
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,64 +0,0 @@
|
|||||||
package login
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"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"
|
|
||||||
)
|
|
||||||
|
|
||||||
// getLDAPConfig gets LDAP config
|
|
||||||
var getLDAPConfig = multildap.GetConfig
|
|
||||||
|
|
||||||
// newLDAP creates multiple LDAP instance
|
|
||||||
var newLDAP = multildap.New
|
|
||||||
|
|
||||||
// logger for the LDAP auth
|
|
||||||
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
|
|
||||||
// populated with the logged in user if successful.
|
|
||||||
var loginUsingLDAP = func(ctx context.Context, query *login.LoginUserQuery,
|
|
||||||
loginService login.Service, cfg *setting.Cfg) (bool, error) {
|
|
||||||
if !cfg.LDAPAuthEnabled {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
config, err := getLDAPConfig(query.Cfg)
|
|
||||||
if err != nil {
|
|
||||||
return true, fmt.Errorf("%v: %w", "Failed to get LDAP config", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
externalUser, err := newLDAP(config.Servers, cfg).Login(query)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, ldap.ErrCouldNotFindUser) {
|
|
||||||
// Ignore the error since user might not be present anyway
|
|
||||||
if err := loginService.DisableExternalUser(ctx, query.Username); err != nil {
|
|
||||||
ldapLogger.Debug("Failed to disable external user", "err", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return invalid credentials if we couldn't find the user anywhere
|
|
||||||
return true, ldap.ErrInvalidCredentials
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, err
|
|
||||||
}
|
|
||||||
|
|
||||||
upsert := &login.UpsertUserCommand{
|
|
||||||
ReqContext: query.ReqContext,
|
|
||||||
ExternalUser: externalUser,
|
|
||||||
SignupAllowed: cfg.LDAPAllowSignup,
|
|
||||||
UserLookupParams: login.UserLookupParams{
|
|
||||||
Login: &externalUser.Login,
|
|
||||||
Email: &externalUser.Email,
|
|
||||||
UserID: nil,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
query.User, err = loginService.UpsertUser(ctx, upsert)
|
|
||||||
return true, err
|
|
||||||
}
|
|
@ -1,160 +0,0 @@
|
|||||||
package login
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/services/ldap"
|
|
||||||
"github.com/grafana/grafana/pkg/services/ldap/multildap"
|
|
||||||
"github.com/grafana/grafana/pkg/services/login"
|
|
||||||
"github.com/grafana/grafana/pkg/services/login/logintest"
|
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
|
||||||
)
|
|
||||||
|
|
||||||
var errTest = errors.New("test error")
|
|
||||||
|
|
||||||
func TestLoginUsingLDAP(t *testing.T) {
|
|
||||||
LDAPLoginScenario(t, "When LDAP enabled and no server configured", func(sc *LDAPLoginScenarioContext) {
|
|
||||||
cfg := setting.NewCfg()
|
|
||||||
cfg.LDAPAuthEnabled = true
|
|
||||||
|
|
||||||
sc.withLoginResult(false)
|
|
||||||
getLDAPConfig = func(*setting.Cfg) (*ldap.Config, error) {
|
|
||||||
config := &ldap.Config{
|
|
||||||
Servers: []*ldap.ServerConfig{},
|
|
||||||
}
|
|
||||||
|
|
||||||
return config, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
loginService := &logintest.LoginServiceFake{}
|
|
||||||
enabled, err := loginUsingLDAP(context.Background(), sc.loginUserQuery, loginService, cfg)
|
|
||||||
require.EqualError(t, err, errTest.Error())
|
|
||||||
|
|
||||||
assert.True(t, enabled)
|
|
||||||
assert.True(t, sc.LDAPAuthenticatorMock.loginCalled)
|
|
||||||
})
|
|
||||||
|
|
||||||
LDAPLoginScenario(t, "When LDAP disabled", func(sc *LDAPLoginScenarioContext) {
|
|
||||||
cfg := setting.NewCfg()
|
|
||||||
cfg.LDAPAuthEnabled = false
|
|
||||||
|
|
||||||
sc.withLoginResult(false)
|
|
||||||
loginService := &logintest.LoginServiceFake{}
|
|
||||||
enabled, err := loginUsingLDAP(context.Background(), sc.loginUserQuery, loginService, cfg)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
assert.False(t, enabled)
|
|
||||||
assert.False(t, sc.LDAPAuthenticatorMock.loginCalled)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
type mockAuth struct {
|
|
||||||
validLogin bool
|
|
||||||
loginCalled bool
|
|
||||||
pingCalled bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (auth *mockAuth) Ping() ([]*multildap.ServerStatus, error) {
|
|
||||||
auth.pingCalled = true
|
|
||||||
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (auth *mockAuth) Login(query *login.LoginUserQuery) (
|
|
||||||
*login.ExternalUserInfo,
|
|
||||||
error,
|
|
||||||
) {
|
|
||||||
auth.loginCalled = true
|
|
||||||
|
|
||||||
if !auth.validLogin {
|
|
||||||
return nil, errTest
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (auth *mockAuth) Users(logins []string) (
|
|
||||||
[]*login.ExternalUserInfo,
|
|
||||||
error,
|
|
||||||
) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (auth *mockAuth) User(login string) (
|
|
||||||
*login.ExternalUserInfo,
|
|
||||||
ldap.ServerConfig,
|
|
||||||
error,
|
|
||||||
) {
|
|
||||||
return nil, ldap.ServerConfig{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (auth *mockAuth) Add(dn string, values map[string][]string) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (auth *mockAuth) Remove(dn string) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func mockLDAPAuthenticator(valid bool) *mockAuth {
|
|
||||||
mock := &mockAuth{
|
|
||||||
validLogin: valid,
|
|
||||||
}
|
|
||||||
|
|
||||||
newLDAP = func(servers []*ldap.ServerConfig, _ *setting.Cfg) multildap.IMultiLDAP {
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
|
|
||||||
type LDAPLoginScenarioContext struct {
|
|
||||||
loginUserQuery *login.LoginUserQuery
|
|
||||||
LDAPAuthenticatorMock *mockAuth
|
|
||||||
}
|
|
||||||
|
|
||||||
type LDAPLoginScenarioFunc func(c *LDAPLoginScenarioContext)
|
|
||||||
|
|
||||||
func LDAPLoginScenario(t *testing.T, desc string, fn LDAPLoginScenarioFunc) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
t.Run(desc, func(t *testing.T) {
|
|
||||||
mock := &mockAuth{}
|
|
||||||
|
|
||||||
sc := &LDAPLoginScenarioContext{
|
|
||||||
loginUserQuery: &login.LoginUserQuery{
|
|
||||||
Username: "user",
|
|
||||||
Password: "pwd",
|
|
||||||
IpAddress: "192.168.1.1:56433",
|
|
||||||
},
|
|
||||||
LDAPAuthenticatorMock: mock,
|
|
||||||
}
|
|
||||||
|
|
||||||
getLDAPConfig = func(*setting.Cfg) (*ldap.Config, error) {
|
|
||||||
config := &ldap.Config{
|
|
||||||
Servers: []*ldap.ServerConfig{
|
|
||||||
{
|
|
||||||
Host: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return config, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
newLDAP = func(server []*ldap.ServerConfig, _ *setting.Cfg) multildap.IMultiLDAP {
|
|
||||||
return mock
|
|
||||||
}
|
|
||||||
|
|
||||||
fn(sc)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sc *LDAPLoginScenarioContext) withLoginResult(valid bool) {
|
|
||||||
sc.LDAPAuthenticatorMock = mockLDAPAuthenticator(valid)
|
|
||||||
}
|
|
@ -26,7 +26,6 @@ import (
|
|||||||
uss "github.com/grafana/grafana/pkg/infra/usagestats/service"
|
uss "github.com/grafana/grafana/pkg/infra/usagestats/service"
|
||||||
"github.com/grafana/grafana/pkg/infra/usagestats/statscollector"
|
"github.com/grafana/grafana/pkg/infra/usagestats/statscollector"
|
||||||
"github.com/grafana/grafana/pkg/infra/usagestats/validator"
|
"github.com/grafana/grafana/pkg/infra/usagestats/validator"
|
||||||
loginpkg "github.com/grafana/grafana/pkg/login"
|
|
||||||
"github.com/grafana/grafana/pkg/login/social"
|
"github.com/grafana/grafana/pkg/login/social"
|
||||||
"github.com/grafana/grafana/pkg/middleware/csrf"
|
"github.com/grafana/grafana/pkg/middleware/csrf"
|
||||||
"github.com/grafana/grafana/pkg/middleware/loggermw"
|
"github.com/grafana/grafana/pkg/middleware/loggermw"
|
||||||
@ -220,8 +219,6 @@ var wireBasicSet = wire.NewSet(
|
|||||||
authinfoservice.ProvideAuthInfoService,
|
authinfoservice.ProvideAuthInfoService,
|
||||||
wire.Bind(new(login.AuthInfoService), new(*authinfoservice.Implementation)),
|
wire.Bind(new(login.AuthInfoService), new(*authinfoservice.Implementation)),
|
||||||
authinfodatabase.ProvideAuthInfoStore,
|
authinfodatabase.ProvideAuthInfoStore,
|
||||||
loginpkg.ProvideService,
|
|
||||||
wire.Bind(new(loginpkg.Authenticator), new(*loginpkg.AuthenticatorService)),
|
|
||||||
datasourceproxy.ProvideService,
|
datasourceproxy.ProvideService,
|
||||||
search.ProvideService,
|
search.ProvideService,
|
||||||
searchV2.ProvideService,
|
searchV2.ProvideService,
|
||||||
|
Loading…
Reference in New Issue
Block a user