package handlers import ( "encoding/json" "fmt" "testing" "time" "github.com/stretchr/testify/suite" "go.uber.org/mock/gomock" "github.com/authelia/authelia/v4/internal/configuration/schema" "github.com/authelia/authelia/v4/internal/middlewares" "github.com/authelia/authelia/v4/internal/mocks" "github.com/authelia/authelia/v4/internal/model" "github.com/authelia/authelia/v4/internal/regulation" ) type HandlerSignPasswordSuite struct { suite.Suite mock *mocks.MockAutheliaCtx } func (s *HandlerSignPasswordSuite) SetupTest() { s.mock = mocks.NewMockAutheliaCtx(s.T()) userSession, err := s.mock.Ctx.GetSession() s.Assert().NoError(err) userSession.Username = testUsername userSession.AuthenticationMethodRefs.WebAuthn = true userSession.AuthenticationMethodRefs.WebAuthnUserPresence = true userSession.AuthenticationMethodRefs.WebAuthnUserVerified = true s.Assert().NoError(s.mock.Ctx.SaveSession(userSession)) s.mock.Clock.Set(time.Unix(1701295903, 0)) s.mock.Ctx.Clock = &s.mock.Clock s.mock.Ctx.Configuration.TOTP = schema.DefaultTOTPConfiguration } func (s *HandlerSignPasswordSuite) TearDownTest() { s.mock.Close() } func (s *HandlerSignPasswordSuite) AssertLastLogMessage(message string, err string) { AssertLogEntryMessageAndError(s.T(), s.mock.Hook.LastEntry(), message, err) } func (s *HandlerSignPasswordSuite) TestShouldRedirectUserToDefaultURL() { gomock.InOrder( s.mock.UserProviderMock. EXPECT(). CheckUserPassword(gomock.Eq("john"), gomock.Eq("123456")). Return(true, nil), s.mock.StorageMock. EXPECT(). AppendAuthenticationLog(s.mock.Ctx, gomock.Eq(model.AuthenticationAttempt{ Username: testUsername, Successful: true, Banned: false, Time: s.mock.Clock.Now(), Type: regulation.AuthTypePassword, RemoteIP: model.NewNullIPFromString("0.0.0.0"), })). Return(nil), ) s.mock.Ctx.Configuration.Session.Cookies[0].DefaultRedirectionURL = testRedirectionURL bodyBytes, err := json.Marshal(bodySecondFactorPasswordRequest{ Password: "123456", }) s.Require().NoError(err) s.mock.Ctx.Request.SetBody(bodyBytes) SecondFactorPasswordPOST(nil)(s.mock.Ctx) s.mock.Assert200OK(s.T(), redirectResponse{ Redirect: testRedirectionURLString, }) } func (s *HandlerSignPasswordSuite) TestShouldHandleOpenIDConnect() { gomock.InOrder( s.mock.UserProviderMock. EXPECT(). CheckUserPassword(gomock.Eq("john"), gomock.Eq("123456")). Return(true, nil), s.mock.StorageMock. EXPECT(). AppendAuthenticationLog(s.mock.Ctx, gomock.Eq(model.AuthenticationAttempt{ Username: testUsername, Successful: true, Banned: false, Time: s.mock.Clock.Now(), Type: regulation.AuthTypePassword, RemoteIP: model.NewNullIPFromString("0.0.0.0"), })). Return(nil), ) s.mock.Ctx.Configuration.Session.Cookies[0].DefaultRedirectionURL = testRedirectionURL bodyBytes, err := json.Marshal(bodySecondFactorPasswordRequest{ Password: "123456", Workflow: workflowOpenIDConnect, WorkflowID: "abc", }) s.Require().NoError(err) s.mock.Ctx.Request.SetBody(bodyBytes) SecondFactorPasswordPOST(nil)(s.mock.Ctx) s.mock.Assert200KO(s.T(), "Authentication failed. Check your credentials.") s.AssertLastLogMessage("unable to parse consent session challenge id 'abc': invalid UUID length: 3", "") } func (s *HandlerSignPasswordSuite) TestShouldRedirectUserToDefaultURLDelayFunc() { gomock.InOrder( s.mock.UserProviderMock. EXPECT(). CheckUserPassword(gomock.Eq("john"), gomock.Eq("123456")). Return(true, nil), s.mock.StorageMock. EXPECT(). AppendAuthenticationLog(s.mock.Ctx, gomock.Eq(model.AuthenticationAttempt{ Username: testUsername, Successful: true, Banned: false, Time: s.mock.Clock.Now(), Type: regulation.AuthTypePassword, RemoteIP: model.NewNullIPFromString("0.0.0.0"), })). Return(nil), ) s.mock.Ctx.Configuration.Session.Cookies[0].DefaultRedirectionURL = testRedirectionURL bodyBytes, err := json.Marshal(bodySecondFactorPasswordRequest{ Password: "123456", }) s.Require().NoError(err) s.mock.Ctx.Request.SetBody(bodyBytes) delayFunc := func(ctx *middlewares.AutheliaCtx, requestTime time.Time, successful *bool) {} SecondFactorPasswordPOST(delayFunc)(s.mock.Ctx) s.mock.Assert200OK(s.T(), redirectResponse{ Redirect: testRedirectionURLString, }) } func (s *HandlerSignPasswordSuite) TestShouldErrorMarkAttempt() { gomock.InOrder( s.mock.UserProviderMock. EXPECT(). CheckUserPassword(gomock.Eq("john"), gomock.Eq("123456")). Return(true, nil), s.mock.StorageMock. EXPECT(). AppendAuthenticationLog(s.mock.Ctx, gomock.Eq(model.AuthenticationAttempt{ Username: testUsername, Successful: true, Banned: false, Time: s.mock.Clock.Now(), Type: regulation.AuthTypePassword, RemoteIP: model.NewNullIPFromString("0.0.0.0"), })). Return(fmt.Errorf("bad socket")), ) s.mock.Ctx.Configuration.Session.Cookies[0].DefaultRedirectionURL = testRedirectionURL bodyBytes, err := json.Marshal(bodySecondFactorPasswordRequest{ Password: "123456", }) s.Require().NoError(err) s.mock.Ctx.Request.SetBody(bodyBytes) SecondFactorPasswordPOST(nil)(s.mock.Ctx) s.mock.Assert200OK(s.T(), &redirectResponse{Redirect: "https://www.example.com"}) s.AssertLastLogMessage("Failed to record Password authentication attempt", "bad socket") } func (s *HandlerSignPasswordSuite) TestShouldHandleBadPassword() { gomock.InOrder( s.mock.UserProviderMock. EXPECT(). CheckUserPassword(gomock.Eq("john"), gomock.Eq("123456")). Return(false, nil), s.mock.StorageMock. EXPECT(). AppendAuthenticationLog(s.mock.Ctx, gomock.Eq(model.AuthenticationAttempt{ Username: testUsername, Successful: false, Banned: false, Time: s.mock.Clock.Now(), Type: regulation.AuthTypePassword, RemoteIP: model.NewNullIPFromString("0.0.0.0"), })). Return(nil), ) s.mock.Ctx.Configuration.Session.Cookies[0].DefaultRedirectionURL = testRedirectionURL bodyBytes, err := json.Marshal(bodySecondFactorPasswordRequest{ Password: "123456", }) s.Require().NoError(err) s.mock.Ctx.Request.SetBody(bodyBytes) SecondFactorPasswordPOST(nil)(s.mock.Ctx) s.mock.Assert401KO(s.T(), "Authentication failed. Check your credentials.") s.AssertLastLogMessage("Unsuccessful Password authentication attempt by user 'john'", "") } func (s *HandlerSignPasswordSuite) TestShouldHandleBadPasswordMarkAttemptError() { gomock.InOrder( s.mock.UserProviderMock. EXPECT(). CheckUserPassword(gomock.Eq("john"), gomock.Eq("123456")). Return(false, nil), s.mock.StorageMock. EXPECT(). AppendAuthenticationLog(s.mock.Ctx, gomock.Eq(model.AuthenticationAttempt{ Username: testUsername, Successful: false, Banned: false, Time: s.mock.Clock.Now(), Type: regulation.AuthTypePassword, RemoteIP: model.NewNullIPFromString("0.0.0.0"), })). Return(fmt.Errorf("bad sockets")), ) s.mock.Ctx.Configuration.Session.Cookies[0].DefaultRedirectionURL = testRedirectionURL bodyBytes, err := json.Marshal(bodySecondFactorPasswordRequest{ Password: "123456", }) s.Require().NoError(err) s.mock.Ctx.Request.SetBody(bodyBytes) SecondFactorPasswordPOST(nil)(s.mock.Ctx) s.mock.Assert401KO(s.T(), "Authentication failed. Check your credentials.") s.AssertLastLogMessage("Unsuccessful Password authentication attempt by user 'john'", "") } func (s *HandlerSignPasswordSuite) TestShouldHandleBadPasswordWithError() { gomock.InOrder( s.mock.UserProviderMock. EXPECT(). CheckUserPassword(gomock.Eq("john"), gomock.Eq("123456")). Return(false, fmt.Errorf("bad user pass")), s.mock.StorageMock. EXPECT(). AppendAuthenticationLog(s.mock.Ctx, gomock.Eq(model.AuthenticationAttempt{ Username: testUsername, Successful: false, Banned: false, Time: s.mock.Clock.Now(), Type: regulation.AuthTypePassword, RemoteIP: model.NewNullIPFromString("0.0.0.0"), })). Return(nil), ) s.mock.Ctx.Configuration.Session.Cookies[0].DefaultRedirectionURL = testRedirectionURL bodyBytes, err := json.Marshal(bodySecondFactorPasswordRequest{ Password: "123456", }) s.Require().NoError(err) s.mock.Ctx.Request.SetBody(bodyBytes) SecondFactorPasswordPOST(nil)(s.mock.Ctx) s.mock.Assert401KO(s.T(), "Authentication failed. Check your credentials.") s.AssertLastLogMessage("Unsuccessful Password authentication attempt by user 'john'", "bad user pass") } func (s *HandlerSignPasswordSuite) TestShouldErrorBadRequestBody() { s.mock.Ctx.Configuration.Session.Cookies[0].DefaultRedirectionURL = testRedirectionURL s.mock.Ctx.Request.SetBody([]byte(`{"password":`)) SecondFactorPasswordPOST(nil)(s.mock.Ctx) s.mock.Assert401KO(s.T(), "Authentication failed. Check your credentials.") s.AssertLastLogMessage("Failed to parse 1FA request body", "unable to parse body: unexpected end of JSON input") } func TestRunHandlerSignPasswordSuite(t *testing.T) { suite.Run(t, new(HandlerSignPasswordSuite)) }