Files
grafana/pkg/login/ldap_login_test.go
Marcus Efraimsson 3d1c624c12 WIP: Protect against brute force (frequent) login attempts (#10031)
* db: add login attempt migrations

* db: add possibility to create login attempts

* db: add possibility to retrieve login attempt count per username

* auth: validation and update of login attempts for invalid credentials

If login attempt count for user authenticating is 5 or more the last 5 minutes
we temporarily block the user access to login

* db: add possibility to delete expired login attempts

* cleanup: Delete login attempts older than 10 minutes

The cleanup job are running continuously and triggering each 10 minute

* fix typo: rename consequent to consequent

* auth: enable login attempt validation for ldap logins

* auth: disable login attempts validation by configuration

Setting is named DisableLoginAttemptsValidation and is false by default
Config disable_login_attempts_validation is placed under security section
#7616

* auth: don't run cleanup of login attempts if feature is disabled

#7616

* auth: rename settings.go to ldap_settings.go

* auth: refactor AuthenticateUser

Extract grafana login, ldap login and login attemp validation together
with their tests to separate files.
Enables testing of many more aspects when authenticating a user.
#7616

* auth: rename login attempt validation to brute force login protection

Setting DisableLoginAttemptsValidation => DisableBruteForceLoginProtection
Configuration disable_login_attempts_validation => disable_brute_force_login_protection
#7616
2018-01-26 10:41:41 +01:00

173 lines
4.0 KiB
Go

package login
import (
"testing"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
. "github.com/smartystreets/goconvey/convey"
)
func TestLdapLogin(t *testing.T) {
Convey("Login using ldap", t, func() {
Convey("Given ldap enabled and a server configured", func() {
setting.LdapEnabled = true
LdapCfg.Servers = append(LdapCfg.Servers,
&LdapServerConf{
Host: "",
})
ldapLoginScenario("When login with invalid credentials", func(sc *ldapLoginScenarioContext) {
sc.withLoginResult(false)
enabled, err := loginUsingLdap(sc.loginUserQuery)
Convey("it should return true", func() {
So(enabled, ShouldBeTrue)
})
Convey("it should return invalid credentials error", func() {
So(err, ShouldEqual, ErrInvalidCredentials)
})
Convey("it should call ldap login", func() {
So(sc.ldapAuthenticatorMock.loginCalled, ShouldBeTrue)
})
})
ldapLoginScenario("When login with valid credentials", func(sc *ldapLoginScenarioContext) {
sc.withLoginResult(true)
enabled, err := loginUsingLdap(sc.loginUserQuery)
Convey("it should return true", func() {
So(enabled, ShouldBeTrue)
})
Convey("it should not return error", func() {
So(err, ShouldBeNil)
})
Convey("it should call ldap login", func() {
So(sc.ldapAuthenticatorMock.loginCalled, ShouldBeTrue)
})
})
})
Convey("Given ldap enabled and no server configured", func() {
setting.LdapEnabled = true
LdapCfg.Servers = make([]*LdapServerConf, 0)
ldapLoginScenario("When login", func(sc *ldapLoginScenarioContext) {
sc.withLoginResult(true)
enabled, err := loginUsingLdap(sc.loginUserQuery)
Convey("it should return true", func() {
So(enabled, ShouldBeTrue)
})
Convey("it should return invalid credentials error", func() {
So(err, ShouldEqual, ErrInvalidCredentials)
})
Convey("it should not call ldap login", func() {
So(sc.ldapAuthenticatorMock.loginCalled, ShouldBeFalse)
})
})
})
Convey("Given ldap disabled", func() {
setting.LdapEnabled = false
ldapLoginScenario("When login", func(sc *ldapLoginScenarioContext) {
sc.withLoginResult(false)
enabled, err := loginUsingLdap(&LoginUserQuery{
Username: "user",
Password: "pwd",
})
Convey("it should return false", func() {
So(enabled, ShouldBeFalse)
})
Convey("it should not return error", func() {
So(err, ShouldBeNil)
})
Convey("it should not call ldap login", func() {
So(sc.ldapAuthenticatorMock.loginCalled, ShouldBeFalse)
})
})
})
})
}
func mockLdapAuthenticator(valid bool) *mockLdapAuther {
mock := &mockLdapAuther{
validLogin: valid,
}
NewLdapAuthenticator = func(server *LdapServerConf) ILdapAuther {
return mock
}
return mock
}
type mockLdapAuther struct {
validLogin bool
loginCalled bool
}
func (a *mockLdapAuther) Login(query *LoginUserQuery) error {
a.loginCalled = true
if !a.validLogin {
return ErrInvalidCredentials
}
return nil
}
func (a *mockLdapAuther) SyncSignedInUser(signedInUser *m.SignedInUser) error {
return nil
}
func (a *mockLdapAuther) GetGrafanaUserFor(ldapUser *LdapUserInfo) (*m.User, error) {
return nil, nil
}
func (a *mockLdapAuther) SyncOrgRoles(user *m.User, ldapUser *LdapUserInfo) error {
return nil
}
type ldapLoginScenarioContext struct {
loginUserQuery *LoginUserQuery
ldapAuthenticatorMock *mockLdapAuther
}
type ldapLoginScenarioFunc func(c *ldapLoginScenarioContext)
func ldapLoginScenario(desc string, fn ldapLoginScenarioFunc) {
Convey(desc, func() {
origNewLdapAuthenticator := NewLdapAuthenticator
sc := &ldapLoginScenarioContext{
loginUserQuery: &LoginUserQuery{
Username: "user",
Password: "pwd",
IpAddress: "192.168.1.1:56433",
},
ldapAuthenticatorMock: &mockLdapAuther{},
}
defer func() {
NewLdapAuthenticator = origNewLdapAuthenticator
}()
fn(sc)
})
}
func (sc *ldapLoginScenarioContext) withLoginResult(valid bool) {
sc.ldapAuthenticatorMock = mockLdapAuthenticator(valid)
}