|
|
|
@ -6,132 +6,127 @@ import ( |
|
|
|
|
"github.com/grafana/grafana/pkg/bus" |
|
|
|
|
"github.com/grafana/grafana/pkg/models" |
|
|
|
|
"github.com/grafana/grafana/pkg/setting" |
|
|
|
|
. "github.com/smartystreets/goconvey/convey" |
|
|
|
|
"github.com/stretchr/testify/assert" |
|
|
|
|
"github.com/stretchr/testify/require" |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
func TestLoginAttemptsValidation(t *testing.T) { |
|
|
|
|
Convey("Validate login attempts", t, func() { |
|
|
|
|
Convey("Given brute force login protection enabled", func() { |
|
|
|
|
cfg := setting.NewCfg() |
|
|
|
|
cfg.DisableBruteForceLoginProtection = false |
|
|
|
|
query := &models.LoginUserQuery{ |
|
|
|
|
Username: "user", |
|
|
|
|
Cfg: cfg, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Convey("When user login attempt count equals max-1 ", func() { |
|
|
|
|
withLoginAttempts(maxInvalidLoginAttempts - 1) |
|
|
|
|
err := validateLoginAttempts(query) |
|
|
|
|
|
|
|
|
|
Convey("it should not result in error", func() { |
|
|
|
|
So(err, ShouldBeNil) |
|
|
|
|
}) |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
Convey("When user login attempt count equals max ", func() { |
|
|
|
|
withLoginAttempts(maxInvalidLoginAttempts) |
|
|
|
|
err := validateLoginAttempts(query) |
|
|
|
|
|
|
|
|
|
Convey("it should result in too many login attempts error", func() { |
|
|
|
|
So(err, ShouldEqual, ErrTooManyLoginAttempts) |
|
|
|
|
}) |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
Convey("When user login attempt count is greater than max ", func() { |
|
|
|
|
withLoginAttempts(maxInvalidLoginAttempts + 5) |
|
|
|
|
err := validateLoginAttempts(query) |
|
|
|
|
|
|
|
|
|
Convey("it should result in too many login attempts error", func() { |
|
|
|
|
So(err, ShouldEqual, ErrTooManyLoginAttempts) |
|
|
|
|
}) |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
Convey("When saving invalid login attempt", func() { |
|
|
|
|
defer bus.ClearBusHandlers() |
|
|
|
|
createLoginAttemptCmd := &models.CreateLoginAttemptCommand{} |
|
|
|
|
|
|
|
|
|
bus.AddHandler("test", func(cmd *models.CreateLoginAttemptCommand) error { |
|
|
|
|
createLoginAttemptCmd = cmd |
|
|
|
|
return nil |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
err := saveInvalidLoginAttempt(&models.LoginUserQuery{ |
|
|
|
|
Username: "user", |
|
|
|
|
Password: "pwd", |
|
|
|
|
IpAddress: "192.168.1.1:56433", |
|
|
|
|
Cfg: setting.NewCfg(), |
|
|
|
|
}) |
|
|
|
|
So(err, ShouldBeNil) |
|
|
|
|
|
|
|
|
|
Convey("it should dispatch command", func() { |
|
|
|
|
So(createLoginAttemptCmd, ShouldNotBeNil) |
|
|
|
|
So(createLoginAttemptCmd.Username, ShouldEqual, "user") |
|
|
|
|
So(createLoginAttemptCmd.IpAddress, ShouldEqual, "192.168.1.1:56433") |
|
|
|
|
}) |
|
|
|
|
}) |
|
|
|
|
func TestValidateLoginAttempts(t *testing.T) { |
|
|
|
|
testCases := []struct { |
|
|
|
|
name string |
|
|
|
|
loginAttempts int64 |
|
|
|
|
cfg *setting.Cfg |
|
|
|
|
expected error |
|
|
|
|
}{ |
|
|
|
|
{ |
|
|
|
|
name: "When brute force protection enabled and user login attempt count is less than max", |
|
|
|
|
loginAttempts: maxInvalidLoginAttempts - 1, |
|
|
|
|
cfg: cfgWithBruteForceLoginProtectionEnabled(t), |
|
|
|
|
expected: nil, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
name: "When brute force protection enabled and user login attempt count equals max", |
|
|
|
|
loginAttempts: maxInvalidLoginAttempts, |
|
|
|
|
cfg: cfgWithBruteForceLoginProtectionEnabled(t), |
|
|
|
|
expected: ErrTooManyLoginAttempts, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
name: "When brute force protection enabled and user login attempt count is greater than max", |
|
|
|
|
loginAttempts: maxInvalidLoginAttempts + 1, |
|
|
|
|
cfg: cfgWithBruteForceLoginProtectionEnabled(t), |
|
|
|
|
expected: ErrTooManyLoginAttempts, |
|
|
|
|
}, |
|
|
|
|
|
|
|
|
|
{ |
|
|
|
|
name: "When brute force protection disabled and user login attempt count is less than max", |
|
|
|
|
loginAttempts: maxInvalidLoginAttempts - 1, |
|
|
|
|
cfg: cfgWithBruteForceLoginProtectionDisabled(t), |
|
|
|
|
expected: nil, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
name: "When brute force protection disabled and user login attempt count equals max", |
|
|
|
|
loginAttempts: maxInvalidLoginAttempts, |
|
|
|
|
cfg: cfgWithBruteForceLoginProtectionDisabled(t), |
|
|
|
|
expected: nil, |
|
|
|
|
}, |
|
|
|
|
{ |
|
|
|
|
name: "When brute force protection disabled and user login attempt count is greater than max", |
|
|
|
|
loginAttempts: maxInvalidLoginAttempts + 1, |
|
|
|
|
cfg: cfgWithBruteForceLoginProtectionDisabled(t), |
|
|
|
|
expected: nil, |
|
|
|
|
}, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
for _, tc := range testCases { |
|
|
|
|
t.Run(tc.name, func(t *testing.T) { |
|
|
|
|
withLoginAttempts(t, tc.loginAttempts) |
|
|
|
|
|
|
|
|
|
query := &models.LoginUserQuery{Username: "user", Cfg: tc.cfg} |
|
|
|
|
err := validateLoginAttempts(query) |
|
|
|
|
require.Equal(t, tc.expected, err) |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func TestSaveInvalidLoginAttempt(t *testing.T) { |
|
|
|
|
t.Run("When brute force protection enabled", func(t *testing.T) { |
|
|
|
|
t.Cleanup(func() { bus.ClearBusHandlers() }) |
|
|
|
|
|
|
|
|
|
createLoginAttemptCmd := &models.CreateLoginAttemptCommand{} |
|
|
|
|
bus.AddHandler("test", func(cmd *models.CreateLoginAttemptCommand) error { |
|
|
|
|
createLoginAttemptCmd = cmd |
|
|
|
|
return nil |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
err := saveInvalidLoginAttempt(&models.LoginUserQuery{ |
|
|
|
|
Username: "user", |
|
|
|
|
Password: "pwd", |
|
|
|
|
IpAddress: "192.168.1.1:56433", |
|
|
|
|
Cfg: cfgWithBruteForceLoginProtectionEnabled(t), |
|
|
|
|
}) |
|
|
|
|
require.NoError(t, err) |
|
|
|
|
|
|
|
|
|
require.NotNil(t, createLoginAttemptCmd) |
|
|
|
|
assert.Equal(t, "user", createLoginAttemptCmd.Username) |
|
|
|
|
assert.Equal(t, "192.168.1.1:56433", createLoginAttemptCmd.IpAddress) |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
Convey("Given brute force login protection disabled", func() { |
|
|
|
|
cfg := setting.NewCfg() |
|
|
|
|
cfg.DisableBruteForceLoginProtection = true |
|
|
|
|
query := &models.LoginUserQuery{ |
|
|
|
|
Username: "user", |
|
|
|
|
Cfg: cfg, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Convey("When user login attempt count equals max-1 ", func() { |
|
|
|
|
withLoginAttempts(maxInvalidLoginAttempts - 1) |
|
|
|
|
err := validateLoginAttempts(query) |
|
|
|
|
|
|
|
|
|
Convey("it should not result in error", func() { |
|
|
|
|
So(err, ShouldBeNil) |
|
|
|
|
}) |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
Convey("When user login attempt count equals max ", func() { |
|
|
|
|
withLoginAttempts(maxInvalidLoginAttempts) |
|
|
|
|
err := validateLoginAttempts(query) |
|
|
|
|
|
|
|
|
|
Convey("it should not result in error", func() { |
|
|
|
|
So(err, ShouldBeNil) |
|
|
|
|
}) |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
Convey("When user login attempt count is greater than max ", func() { |
|
|
|
|
withLoginAttempts(maxInvalidLoginAttempts + 5) |
|
|
|
|
err := validateLoginAttempts(query) |
|
|
|
|
|
|
|
|
|
Convey("it should not result in error", func() { |
|
|
|
|
So(err, ShouldBeNil) |
|
|
|
|
}) |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
Convey("When saving invalid login attempt", func() { |
|
|
|
|
defer bus.ClearBusHandlers() |
|
|
|
|
var createLoginAttemptCmd *models.CreateLoginAttemptCommand |
|
|
|
|
|
|
|
|
|
bus.AddHandler("test", func(cmd *models.CreateLoginAttemptCommand) error { |
|
|
|
|
createLoginAttemptCmd = cmd |
|
|
|
|
return nil |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
err := saveInvalidLoginAttempt(&models.LoginUserQuery{ |
|
|
|
|
Username: "user", |
|
|
|
|
Password: "pwd", |
|
|
|
|
IpAddress: "192.168.1.1:56433", |
|
|
|
|
Cfg: cfg, |
|
|
|
|
}) |
|
|
|
|
So(err, ShouldBeNil) |
|
|
|
|
|
|
|
|
|
Convey("it should not dispatch command", func() { |
|
|
|
|
So(createLoginAttemptCmd, ShouldBeNil) |
|
|
|
|
}) |
|
|
|
|
}) |
|
|
|
|
t.Run("When brute force protection disabled", func(t *testing.T) { |
|
|
|
|
t.Cleanup(func() { bus.ClearBusHandlers() }) |
|
|
|
|
|
|
|
|
|
var createLoginAttemptCmd *models.CreateLoginAttemptCommand |
|
|
|
|
bus.AddHandler("test", func(cmd *models.CreateLoginAttemptCommand) error { |
|
|
|
|
createLoginAttemptCmd = cmd |
|
|
|
|
return nil |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
err := saveInvalidLoginAttempt(&models.LoginUserQuery{ |
|
|
|
|
Username: "user", |
|
|
|
|
Password: "pwd", |
|
|
|
|
IpAddress: "192.168.1.1:56433", |
|
|
|
|
Cfg: cfgWithBruteForceLoginProtectionDisabled(t), |
|
|
|
|
}) |
|
|
|
|
require.NoError(t, err) |
|
|
|
|
|
|
|
|
|
require.Nil(t, createLoginAttemptCmd) |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func withLoginAttempts(loginAttempts int64) { |
|
|
|
|
func cfgWithBruteForceLoginProtectionDisabled(t *testing.T) *setting.Cfg { |
|
|
|
|
t.Helper() |
|
|
|
|
cfg := setting.NewCfg() |
|
|
|
|
cfg.DisableBruteForceLoginProtection = true |
|
|
|
|
return cfg |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func cfgWithBruteForceLoginProtectionEnabled(t *testing.T) *setting.Cfg { |
|
|
|
|
t.Helper() |
|
|
|
|
cfg := setting.NewCfg() |
|
|
|
|
require.False(t, cfg.DisableBruteForceLoginProtection) |
|
|
|
|
return cfg |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func withLoginAttempts(t *testing.T, loginAttempts int64) { |
|
|
|
|
t.Helper() |
|
|
|
|
bus.AddHandler("test", func(query *models.GetUserLoginAttemptCountQuery) error { |
|
|
|
|
query.Result = loginAttempts |
|
|
|
|
return nil |
|
|
|
|