mirror of https://github.com/grafana/grafana
Auth: Remove unused Authenticator service (#73143)
Auth: remove unused Authenticator servicepull/73145/head
parent
d29f4a8f76
commit
43aab615c3
@ -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) |
||||
} |
Loading…
Reference in new issue