[SECURITY] Fix Authentication HTTP Status Codes (#959)
* [FIX] Send correct HTTP status codes for 1FA * use harmonious func to handle all 1FA attempt errors * use same harmonious func to handle 2FA attempt errors * always send a 401 which is correct according to https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401 * fix tests * refactor isTargetURLAuthorized * fix padding and imports * harmonize remaining return messages * fixup docs and layout of verifySessionHasUpToDateProfilepull/977/head^2
parent
7ac6c16e24
commit
50f12bc4a4
|
@ -17,7 +17,7 @@ func FirstFactorPost(ctx *middlewares.AutheliaCtx) {
|
||||||
err := ctx.ParseBody(&bodyJSON)
|
err := ctx.ParseBody(&bodyJSON)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(err, authenticationFailedMessage)
|
handleAuthenticationUnauthorized(ctx, err, authenticationFailedMessage)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,11 +25,11 @@ func FirstFactorPost(ctx *middlewares.AutheliaCtx) {
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == regulation.ErrUserIsBanned {
|
if err == regulation.ErrUserIsBanned {
|
||||||
ctx.Error(fmt.Errorf("User %s is banned until %s", bodyJSON.Username, bannedUntil), userBannedMessage)
|
handleAuthenticationUnauthorized(ctx, fmt.Errorf("User %s is banned until %s", bodyJSON.Username, bannedUntil), userBannedMessage)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Error(fmt.Errorf("Unable to regulate authentication: %s", err), authenticationFailedMessage)
|
handleAuthenticationUnauthorized(ctx, fmt.Errorf("Unable to regulate authentication: %s", err.Error()), authenticationFailedMessage)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ func FirstFactorPost(ctx *middlewares.AutheliaCtx) {
|
||||||
ctx.Logger.Debugf("Mark authentication attempt made by user %s", bodyJSON.Username)
|
ctx.Logger.Debugf("Mark authentication attempt made by user %s", bodyJSON.Username)
|
||||||
ctx.Providers.Regulator.Mark(bodyJSON.Username, false) //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
ctx.Providers.Regulator.Mark(bodyJSON.Username, false) //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
||||||
|
|
||||||
ctx.Error(fmt.Errorf("Error while checking password for user %s: %s", bodyJSON.Username, err.Error()), authenticationFailedMessage)
|
handleAuthenticationUnauthorized(ctx, fmt.Errorf("Error while checking password for user %s: %s", bodyJSON.Username, err.Error()), authenticationFailedMessage)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -49,6 +49,8 @@ func FirstFactorPost(ctx *middlewares.AutheliaCtx) {
|
||||||
ctx.Logger.Debugf("Mark authentication attempt made by user %s", bodyJSON.Username)
|
ctx.Logger.Debugf("Mark authentication attempt made by user %s", bodyJSON.Username)
|
||||||
ctx.Providers.Regulator.Mark(bodyJSON.Username, false) //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
ctx.Providers.Regulator.Mark(bodyJSON.Username, false) //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
|
||||||
|
|
||||||
|
handleAuthenticationUnauthorized(ctx, fmt.Errorf("Credentials are wrong for user %s", bodyJSON.Username), authenticationFailedMessage)
|
||||||
|
|
||||||
ctx.ReplyError(fmt.Errorf("Credentials are wrong for user %s", bodyJSON.Username), authenticationFailedMessage)
|
ctx.ReplyError(fmt.Errorf("Credentials are wrong for user %s", bodyJSON.Username), authenticationFailedMessage)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -60,7 +62,7 @@ func FirstFactorPost(ctx *middlewares.AutheliaCtx) {
|
||||||
err = ctx.Providers.Regulator.Mark(bodyJSON.Username, true)
|
err = ctx.Providers.Regulator.Mark(bodyJSON.Username, true)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(fmt.Errorf("Unable to mark authentication: %s", err), authenticationFailedMessage)
|
handleAuthenticationUnauthorized(ctx, fmt.Errorf("Unable to mark authentication: %s", err.Error()), authenticationFailedMessage)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,14 +70,14 @@ func FirstFactorPost(ctx *middlewares.AutheliaCtx) {
|
||||||
err = ctx.SaveSession(session.NewDefaultUserSession())
|
err = ctx.SaveSession(session.NewDefaultUserSession())
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(fmt.Errorf("Unable to reset the session for user %s: %s", bodyJSON.Username, err), authenticationFailedMessage)
|
handleAuthenticationUnauthorized(ctx, fmt.Errorf("Unable to reset the session for user %s: %s", bodyJSON.Username, err.Error()), authenticationFailedMessage)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ctx.Providers.SessionProvider.RegenerateSession(ctx.RequestCtx)
|
err = ctx.Providers.SessionProvider.RegenerateSession(ctx.RequestCtx)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(fmt.Errorf("Unable to regenerate session for user %s: %s", bodyJSON.Username, err), authenticationFailedMessage)
|
handleAuthenticationUnauthorized(ctx, fmt.Errorf("Unable to regenerate session for user %s: %s", bodyJSON.Username, err.Error()), authenticationFailedMessage)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,7 +88,7 @@ func FirstFactorPost(ctx *middlewares.AutheliaCtx) {
|
||||||
if keepMeLoggedIn {
|
if keepMeLoggedIn {
|
||||||
err = ctx.Providers.SessionProvider.UpdateExpiration(ctx.RequestCtx, ctx.Providers.SessionProvider.RememberMe)
|
err = ctx.Providers.SessionProvider.UpdateExpiration(ctx.RequestCtx, ctx.Providers.SessionProvider.RememberMe)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(fmt.Errorf("Unable to update expiration timer for user %s: %s", bodyJSON.Username, err), authenticationFailedMessage)
|
handleAuthenticationUnauthorized(ctx, fmt.Errorf("Unable to update expiration timer for user %s: %s", bodyJSON.Username, err.Error()), authenticationFailedMessage)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -95,7 +97,7 @@ func FirstFactorPost(ctx *middlewares.AutheliaCtx) {
|
||||||
userDetails, err := ctx.Providers.UserProvider.GetDetails(bodyJSON.Username)
|
userDetails, err := ctx.Providers.UserProvider.GetDetails(bodyJSON.Username)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(fmt.Errorf("Error while retrieving details from user %s: %s", bodyJSON.Username, err.Error()), authenticationFailedMessage)
|
handleAuthenticationUnauthorized(ctx, fmt.Errorf("Error while retrieving details from user %s: %s", bodyJSON.Username, err.Error()), authenticationFailedMessage)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,7 +120,7 @@ func FirstFactorPost(ctx *middlewares.AutheliaCtx) {
|
||||||
err = ctx.SaveSession(userSession)
|
err = ctx.SaveSession(userSession)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(fmt.Errorf("Unable to save session of user %s", bodyJSON.Username), authenticationFailedMessage)
|
handleAuthenticationUnauthorized(ctx, fmt.Errorf("Unable to save session of user %s", bodyJSON.Username), authenticationFailedMessage)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ func (s *FirstFactorSuite) TestShouldFailIfBodyIsNil() {
|
||||||
|
|
||||||
// No body
|
// No body
|
||||||
assert.Equal(s.T(), "Unable to parse body: unexpected end of JSON input", s.mock.Hook.LastEntry().Message)
|
assert.Equal(s.T(), "Unable to parse body: unexpected end of JSON input", s.mock.Hook.LastEntry().Message)
|
||||||
s.mock.Assert200KO(s.T(), "Authentication failed. Check your credentials.")
|
s.mock.Assert401KO(s.T(), "Authentication failed. Check your credentials.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FirstFactorSuite) TestShouldFailIfBodyIsInBadFormat() {
|
func (s *FirstFactorSuite) TestShouldFailIfBodyIsInBadFormat() {
|
||||||
|
@ -45,7 +45,7 @@ func (s *FirstFactorSuite) TestShouldFailIfBodyIsInBadFormat() {
|
||||||
FirstFactorPost(s.mock.Ctx)
|
FirstFactorPost(s.mock.Ctx)
|
||||||
|
|
||||||
assert.Equal(s.T(), "Unable to validate body: password: non zero value required", s.mock.Hook.LastEntry().Message)
|
assert.Equal(s.T(), "Unable to validate body: password: non zero value required", s.mock.Hook.LastEntry().Message)
|
||||||
s.mock.Assert200KO(s.T(), "Authentication failed. Check your credentials.")
|
s.mock.Assert401KO(s.T(), "Authentication failed. Check your credentials.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FirstFactorSuite) TestShouldFailIfUserProviderCheckPasswordFail() {
|
func (s *FirstFactorSuite) TestShouldFailIfUserProviderCheckPasswordFail() {
|
||||||
|
@ -70,7 +70,7 @@ func (s *FirstFactorSuite) TestShouldFailIfUserProviderCheckPasswordFail() {
|
||||||
FirstFactorPost(s.mock.Ctx)
|
FirstFactorPost(s.mock.Ctx)
|
||||||
|
|
||||||
assert.Equal(s.T(), "Error while checking password for user test: Failed", s.mock.Hook.LastEntry().Message)
|
assert.Equal(s.T(), "Error while checking password for user test: Failed", s.mock.Hook.LastEntry().Message)
|
||||||
s.mock.Assert200KO(s.T(), "Authentication failed. Check your credentials.")
|
s.mock.Assert401KO(s.T(), "Authentication failed. Check your credentials.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FirstFactorSuite) TestShouldCheckAuthenticationIsMarkedWhenInvalidCredentials() {
|
func (s *FirstFactorSuite) TestShouldCheckAuthenticationIsMarkedWhenInvalidCredentials() {
|
||||||
|
@ -120,7 +120,7 @@ func (s *FirstFactorSuite) TestShouldFailIfUserProviderGetDetailsFail() {
|
||||||
FirstFactorPost(s.mock.Ctx)
|
FirstFactorPost(s.mock.Ctx)
|
||||||
|
|
||||||
assert.Equal(s.T(), "Error while retrieving details from user test: Failed", s.mock.Hook.LastEntry().Message)
|
assert.Equal(s.T(), "Error while retrieving details from user test: Failed", s.mock.Hook.LastEntry().Message)
|
||||||
s.mock.Assert200KO(s.T(), "Authentication failed. Check your credentials.")
|
s.mock.Assert401KO(s.T(), "Authentication failed. Check your credentials.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FirstFactorSuite) TestShouldFailIfAuthenticationMarkFail() {
|
func (s *FirstFactorSuite) TestShouldFailIfAuthenticationMarkFail() {
|
||||||
|
@ -142,7 +142,7 @@ func (s *FirstFactorSuite) TestShouldFailIfAuthenticationMarkFail() {
|
||||||
FirstFactorPost(s.mock.Ctx)
|
FirstFactorPost(s.mock.Ctx)
|
||||||
|
|
||||||
assert.Equal(s.T(), "Unable to mark authentication: failed", s.mock.Hook.LastEntry().Message)
|
assert.Equal(s.T(), "Unable to mark authentication: failed", s.mock.Hook.LastEntry().Message)
|
||||||
s.mock.Assert200KO(s.T(), "Authentication failed. Check your credentials.")
|
s.mock.Assert401KO(s.T(), "Authentication failed. Check your credentials.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FirstFactorSuite) TestShouldAuthenticateUserWithRememberMeChecked() {
|
func (s *FirstFactorSuite) TestShouldAuthenticateUserWithRememberMeChecked() {
|
||||||
|
|
|
@ -16,7 +16,7 @@ func SecondFactorDuoPost(duoAPI duo.API) middlewares.RequestHandler {
|
||||||
err := ctx.ParseBody(&requestBody)
|
err := ctx.ParseBody(&requestBody)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(err, mfaValidationFailedMessage)
|
handleAuthenticationUnauthorized(ctx, err, mfaValidationFailedMessage)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ func SecondFactorDuoPost(duoAPI duo.API) middlewares.RequestHandler {
|
||||||
|
|
||||||
duoResponse, err := duoAPI.Call(values, ctx)
|
duoResponse, err := duoAPI.Call(values, ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(fmt.Errorf("Duo API errored: %s", err), mfaValidationFailedMessage)
|
handleAuthenticationUnauthorized(ctx, fmt.Errorf("Duo API errored: %s", err), mfaValidationFailedMessage)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ func SecondFactorDuoPost(duoAPI duo.API) middlewares.RequestHandler {
|
||||||
err = ctx.Providers.SessionProvider.RegenerateSession(ctx.RequestCtx)
|
err = ctx.Providers.SessionProvider.RegenerateSession(ctx.RequestCtx)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(fmt.Errorf("Unable to regenerate session for user %s: %s", userSession.Username, err), authenticationFailedMessage)
|
handleAuthenticationUnauthorized(ctx, fmt.Errorf("Unable to regenerate session for user %s: %s", userSession.Username, err), mfaValidationFailedMessage)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ func SecondFactorDuoPost(duoAPI duo.API) middlewares.RequestHandler {
|
||||||
err = ctx.SaveSession(userSession)
|
err = ctx.SaveSession(userSession)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(fmt.Errorf("Unable to update authentication level with Duo: %s", err), mfaValidationFailedMessage)
|
handleAuthenticationUnauthorized(ctx, fmt.Errorf("Unable to update authentication level with Duo: %s", err), mfaValidationFailedMessage)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -92,7 +92,7 @@ func (s *SecondFactorDuoPostSuite) TestShouldCallDuoAPIAndFail() {
|
||||||
|
|
||||||
SecondFactorDuoPost(duoMock)(s.mock.Ctx)
|
SecondFactorDuoPost(duoMock)(s.mock.Ctx)
|
||||||
|
|
||||||
s.mock.Assert200KO(s.T(), "Authentication failed, please retry later.")
|
s.mock.Assert401KO(s.T(), "Authentication failed, please retry later.")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SecondFactorDuoPostSuite) TestShouldRedirectUserToDefaultURL() {
|
func (s *SecondFactorDuoPostSuite) TestShouldRedirectUserToDefaultURL() {
|
||||||
|
|
|
@ -14,7 +14,7 @@ func SecondFactorTOTPPost(totpVerifier TOTPVerifier) middlewares.RequestHandler
|
||||||
err := ctx.ParseBody(&bodyJSON)
|
err := ctx.ParseBody(&bodyJSON)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(err, mfaValidationFailedMessage)
|
handleAuthenticationUnauthorized(ctx, err, mfaValidationFailedMessage)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,25 +22,25 @@ func SecondFactorTOTPPost(totpVerifier TOTPVerifier) middlewares.RequestHandler
|
||||||
|
|
||||||
secret, err := ctx.Providers.StorageProvider.LoadTOTPSecret(userSession.Username)
|
secret, err := ctx.Providers.StorageProvider.LoadTOTPSecret(userSession.Username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(fmt.Errorf("Unable to load TOTP secret: %s", err), mfaValidationFailedMessage)
|
handleAuthenticationUnauthorized(ctx, fmt.Errorf("Unable to load TOTP secret: %s", err), mfaValidationFailedMessage)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
isValid, err := totpVerifier.Verify(bodyJSON.Token, secret)
|
isValid, err := totpVerifier.Verify(bodyJSON.Token, secret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(fmt.Errorf("Error occurred during OTP validation for user %s: %s", userSession.Username, err), mfaValidationFailedMessage)
|
handleAuthenticationUnauthorized(ctx, fmt.Errorf("Error occurred during OTP validation for user %s: %s", userSession.Username, err), mfaValidationFailedMessage)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isValid {
|
if !isValid {
|
||||||
ctx.Error(fmt.Errorf("Wrong passcode during TOTP validation for user %s", userSession.Username), mfaValidationFailedMessage)
|
handleAuthenticationUnauthorized(ctx, fmt.Errorf("Wrong passcode during TOTP validation for user %s", userSession.Username), mfaValidationFailedMessage)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ctx.Providers.SessionProvider.RegenerateSession(ctx.RequestCtx)
|
err = ctx.Providers.SessionProvider.RegenerateSession(ctx.RequestCtx)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(fmt.Errorf("Unable to regenerate session for user %s: %s", userSession.Username, err), authenticationFailedMessage)
|
handleAuthenticationUnauthorized(ctx, fmt.Errorf("Unable to regenerate session for user %s: %s", userSession.Username, err), mfaValidationFailedMessage)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ func SecondFactorTOTPPost(totpVerifier TOTPVerifier) middlewares.RequestHandler
|
||||||
err = ctx.SaveSession(userSession)
|
err = ctx.SaveSession(userSession)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(fmt.Errorf("Unable to update the authentication level with TOTP: %s", err), mfaValidationFailedMessage)
|
handleAuthenticationUnauthorized(ctx, fmt.Errorf("Unable to update the authentication level with TOTP: %s", err), mfaValidationFailedMessage)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,12 +14,12 @@ import (
|
||||||
// SecondFactorU2FSignGet handler for initiating a signing request.
|
// SecondFactorU2FSignGet handler for initiating a signing request.
|
||||||
func SecondFactorU2FSignGet(ctx *middlewares.AutheliaCtx) {
|
func SecondFactorU2FSignGet(ctx *middlewares.AutheliaCtx) {
|
||||||
if ctx.XForwardedProto() == nil {
|
if ctx.XForwardedProto() == nil {
|
||||||
ctx.Error(errMissingXForwardedProto, operationFailedMessage)
|
ctx.Error(errMissingXForwardedProto, mfaValidationFailedMessage)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.XForwardedHost() == nil {
|
if ctx.XForwardedHost() == nil {
|
||||||
ctx.Error(errMissingXForwardedHost, operationFailedMessage)
|
ctx.Error(errMissingXForwardedHost, mfaValidationFailedMessage)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ func SecondFactorU2FSignGet(ctx *middlewares.AutheliaCtx) {
|
||||||
challenge, err := u2f.NewChallenge(appID, trustedFacets)
|
challenge, err := u2f.NewChallenge(appID, trustedFacets)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(fmt.Errorf("Unable to create U2F challenge: %s", err), mfaValidationFailedMessage)
|
handleAuthenticationUnauthorized(ctx, fmt.Errorf("Unable to create U2F challenge: %s", err), mfaValidationFailedMessage)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,11 +38,11 @@ func SecondFactorU2FSignGet(ctx *middlewares.AutheliaCtx) {
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == storage.ErrNoU2FDeviceHandle {
|
if err == storage.ErrNoU2FDeviceHandle {
|
||||||
ctx.Error(fmt.Errorf("No device handle found for user %s", userSession.Username), mfaValidationFailedMessage)
|
handleAuthenticationUnauthorized(ctx, fmt.Errorf("No device handle found for user %s", userSession.Username), mfaValidationFailedMessage)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Error(fmt.Errorf("Unable to retrieve U2F device handle: %s", err), mfaValidationFailedMessage)
|
handleAuthenticationUnauthorized(ctx, fmt.Errorf("Unable to retrieve U2F device handle: %s", err), mfaValidationFailedMessage)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -63,7 +63,7 @@ func SecondFactorU2FSignGet(ctx *middlewares.AutheliaCtx) {
|
||||||
err = ctx.SaveSession(userSession)
|
err = ctx.SaveSession(userSession)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(fmt.Errorf("Unable to save U2F challenge and registration in session: %s", err), mfaValidationFailedMessage)
|
handleAuthenticationUnauthorized(ctx, fmt.Errorf("Unable to save U2F challenge and registration in session: %s", err), mfaValidationFailedMessage)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,7 +71,7 @@ func SecondFactorU2FSignGet(ctx *middlewares.AutheliaCtx) {
|
||||||
err = ctx.SetJSONBody(signRequest)
|
err = ctx.SetJSONBody(signRequest)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(fmt.Errorf("Unable to set sign request in body: %s", err), mfaValidationFailedMessage)
|
handleAuthenticationUnauthorized(ctx, fmt.Errorf("Unable to set sign request in body: %s", err), mfaValidationFailedMessage)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,12 +20,12 @@ func SecondFactorU2FSignPost(u2fVerifier U2FVerifier) middlewares.RequestHandler
|
||||||
|
|
||||||
userSession := ctx.GetSession()
|
userSession := ctx.GetSession()
|
||||||
if userSession.U2FChallenge == nil {
|
if userSession.U2FChallenge == nil {
|
||||||
ctx.Error(fmt.Errorf("U2F signing has not been initiated yet (no challenge)"), mfaValidationFailedMessage)
|
handleAuthenticationUnauthorized(ctx, fmt.Errorf("U2F signing has not been initiated yet (no challenge)"), mfaValidationFailedMessage)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if userSession.U2FRegistration == nil {
|
if userSession.U2FRegistration == nil {
|
||||||
ctx.Error(fmt.Errorf("U2F signing has not been initiated yet (no registration)"), mfaValidationFailedMessage)
|
handleAuthenticationUnauthorized(ctx, fmt.Errorf("U2F signing has not been initiated yet (no registration)"), mfaValidationFailedMessage)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ func SecondFactorU2FSignPost(u2fVerifier U2FVerifier) middlewares.RequestHandler
|
||||||
err = ctx.Providers.SessionProvider.RegenerateSession(ctx.RequestCtx)
|
err = ctx.Providers.SessionProvider.RegenerateSession(ctx.RequestCtx)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(fmt.Errorf("Unable to regenerate session for user %s: %s", userSession.Username, err), authenticationFailedMessage)
|
handleAuthenticationUnauthorized(ctx, fmt.Errorf("Unable to regenerate session for user %s: %s", userSession.Username, err), mfaValidationFailedMessage)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ func SecondFactorU2FSignPost(u2fVerifier U2FVerifier) middlewares.RequestHandler
|
||||||
err = ctx.SaveSession(userSession)
|
err = ctx.SaveSession(userSession)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(fmt.Errorf("Unable to update authentication level with U2F: %s", err), mfaValidationFailedMessage)
|
handleAuthenticationUnauthorized(ctx, fmt.Errorf("Unable to update authentication level with U2F: %s", err), mfaValidationFailedMessage)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -104,9 +104,10 @@ func isTargetURLAuthorized(authorizer *authorization.Authorizer, targetURL url.U
|
||||||
IP: clientIP,
|
IP: clientIP,
|
||||||
}, targetURL)
|
}, targetURL)
|
||||||
|
|
||||||
if level == authorization.Bypass {
|
switch {
|
||||||
|
case level == authorization.Bypass:
|
||||||
return Authorized
|
return Authorized
|
||||||
} else if username != "" && level == authorization.Denied {
|
case level == authorization.Denied && username != "":
|
||||||
// If the user is not anonymous, it means that we went through
|
// If the user is not anonymous, it means that we went through
|
||||||
// all the rules related to that user and knowing who he is we can
|
// all the rules related to that user and knowing who he is we can
|
||||||
// deduce the access is forbidden
|
// deduce the access is forbidden
|
||||||
|
@ -114,14 +115,9 @@ func isTargetURLAuthorized(authorizer *authorization.Authorizer, targetURL url.U
|
||||||
// could not be granted the rights to access the resource. Consequently
|
// could not be granted the rights to access the resource. Consequently
|
||||||
// for anonymous users we send Unauthorized instead of Forbidden
|
// for anonymous users we send Unauthorized instead of Forbidden
|
||||||
return Forbidden
|
return Forbidden
|
||||||
} else {
|
case level == authorization.OneFactor && authLevel >= authentication.OneFactor,
|
||||||
if level == authorization.OneFactor &&
|
level == authorization.TwoFactor && authLevel >= authentication.TwoFactor:
|
||||||
authLevel >= authentication.OneFactor {
|
|
||||||
return Authorized
|
return Authorized
|
||||||
} else if level == authorization.TwoFactor &&
|
|
||||||
authLevel >= authentication.TwoFactor {
|
|
||||||
return Authorized
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return NotAuthorized
|
return NotAuthorized
|
||||||
|
@ -186,7 +182,8 @@ func hasUserBeenInactiveTooLong(ctx *middlewares.AutheliaCtx) (bool, error) { //
|
||||||
}
|
}
|
||||||
|
|
||||||
// verifySessionCookie verifies if a user is identified by a cookie.
|
// verifySessionCookie verifies if a user is identified by a cookie.
|
||||||
func verifySessionCookie(ctx *middlewares.AutheliaCtx, targetURL *url.URL, userSession *session.UserSession, refreshProfile bool, refreshProfileInterval time.Duration) (username string, groups []string, authLevel authentication.Level, err error) {
|
func verifySessionCookie(ctx *middlewares.AutheliaCtx, targetURL *url.URL, userSession *session.UserSession, refreshProfile bool,
|
||||||
|
refreshProfileInterval time.Duration) (username string, groups []string, authLevel authentication.Level, err error) {
|
||||||
// No username in the session means the user is anonymous.
|
// No username in the session means the user is anonymous.
|
||||||
isUserAnonymous := userSession.Username == ""
|
isUserAnonymous := userSession.Username == ""
|
||||||
|
|
||||||
|
@ -326,6 +323,15 @@ func verifySessionHasUpToDateProfile(ctx *middlewares.AutheliaCtx, targetURL *ur
|
||||||
|
|
||||||
if !groupsDiff && !emailsDiff {
|
if !groupsDiff && !emailsDiff {
|
||||||
ctx.Logger.Tracef("Updated profile not detected for %s.", userSession.Username)
|
ctx.Logger.Tracef("Updated profile not detected for %s.", userSession.Username)
|
||||||
|
// Only update TTL if the user has a interval set.
|
||||||
|
// We get to this check when there were no changes.
|
||||||
|
// Also make sure to update the session even if no difference was found.
|
||||||
|
// This is so that we don't check every subsequent request after this one.
|
||||||
|
if refreshProfileInterval != schema.RefreshIntervalAlways {
|
||||||
|
// Update RefreshTTL and save session if refresh is not set to always.
|
||||||
|
userSession.RefreshTTL = ctx.Clock.Now().Add(refreshProfileInterval)
|
||||||
|
return ctx.SaveSession(*userSession)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ctx.Logger.Debugf("Updated profile detected for %s.", userSession.Username)
|
ctx.Logger.Debugf("Updated profile detected for %s.", userSession.Username)
|
||||||
if ctx.Logger.Level.String() == "trace" {
|
if ctx.Logger.Level.String() == "trace" {
|
||||||
|
@ -338,17 +344,12 @@ func verifySessionHasUpToDateProfile(ctx *middlewares.AutheliaCtx, targetURL *ur
|
||||||
if refreshProfileInterval != schema.RefreshIntervalAlways {
|
if refreshProfileInterval != schema.RefreshIntervalAlways {
|
||||||
userSession.RefreshTTL = ctx.Clock.Now().Add(refreshProfileInterval)
|
userSession.RefreshTTL = ctx.Clock.Now().Add(refreshProfileInterval)
|
||||||
}
|
}
|
||||||
return ctx.SaveSession(*userSession)
|
// Return the result of save session if there were changes.
|
||||||
}
|
|
||||||
// Only update TTL if the user has a interval set.
|
|
||||||
// Also make sure to update the session even if no difference was found.
|
|
||||||
// This is so that we don't check every subsequent request after this one.
|
|
||||||
if refreshProfileInterval != schema.RefreshIntervalAlways {
|
|
||||||
userSession.RefreshTTL = ctx.Clock.Now().Add(refreshProfileInterval)
|
|
||||||
return ctx.SaveSession(*userSession)
|
return ctx.SaveSession(*userSession)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return nil if disabled or if no changes and refresh interval set to always.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -431,12 +432,13 @@ func VerifyGet(cfg schema.AuthenticationBackendConfiguration) middlewares.Reques
|
||||||
authorization := isTargetURLAuthorized(ctx.Providers.Authorizer, *targetURL, username,
|
authorization := isTargetURLAuthorized(ctx.Providers.Authorizer, *targetURL, username,
|
||||||
groups, ctx.RemoteIP(), authLevel)
|
groups, ctx.RemoteIP(), authLevel)
|
||||||
|
|
||||||
if authorization == Forbidden {
|
switch authorization {
|
||||||
|
case Forbidden:
|
||||||
ctx.Logger.Infof("Access to %s is forbidden to user %s", targetURL.String(), username)
|
ctx.Logger.Infof("Access to %s is forbidden to user %s", targetURL.String(), username)
|
||||||
ctx.ReplyForbidden()
|
ctx.ReplyForbidden()
|
||||||
} else if authorization == NotAuthorized {
|
case NotAuthorized:
|
||||||
handleUnauthorized(ctx, targetURL, username)
|
handleUnauthorized(ctx, targetURL, username)
|
||||||
} else if authorization == Authorized {
|
case Authorized:
|
||||||
setForwardedHeaders(&ctx.Response.Header, username, groups)
|
setForwardedHeaders(&ctx.Response.Header, username, groups)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/valyala/fasthttp"
|
||||||
|
|
||||||
"github.com/authelia/authelia/internal/authorization"
|
"github.com/authelia/authelia/internal/authorization"
|
||||||
"github.com/authelia/authelia/internal/middlewares"
|
"github.com/authelia/authelia/internal/middlewares"
|
||||||
"github.com/authelia/authelia/internal/utils"
|
"github.com/authelia/authelia/internal/utils"
|
||||||
|
@ -85,3 +87,9 @@ func Handle2FAResponse(ctx *middlewares.AutheliaCtx, targetURI string) {
|
||||||
ctx.ReplyOK()
|
ctx.ReplyOK()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handleAuthenticationUnauthorized provides harmonized response codes for 1FA.
|
||||||
|
func handleAuthenticationUnauthorized(ctx *middlewares.AutheliaCtx, err error, message string) {
|
||||||
|
ctx.SetStatusCode(fasthttp.StatusUnauthorized)
|
||||||
|
ctx.Error(err, message)
|
||||||
|
}
|
||||||
|
|
|
@ -133,6 +133,12 @@ func (m *MockAutheliaCtx) Close() {
|
||||||
m.Ctrl.Finish()
|
m.Ctrl.Finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Assert401KO assert an error response from the service.
|
||||||
|
func (m *MockAutheliaCtx) Assert401KO(t *testing.T, message string) {
|
||||||
|
assert.Equal(t, 401, m.Ctx.Response.StatusCode())
|
||||||
|
assert.Equal(t, fmt.Sprintf("{\"status\":\"KO\",\"message\":\"%s\"}", message), string(m.Ctx.Response.Body()))
|
||||||
|
}
|
||||||
|
|
||||||
// Assert200KO assert an error response from the service.
|
// Assert200KO assert an error response from the service.
|
||||||
func (m *MockAutheliaCtx) Assert200KO(t *testing.T, message string) {
|
func (m *MockAutheliaCtx) Assert200KO(t *testing.T, message string) {
|
||||||
assert.Equal(t, 200, m.Ctx.Response.StatusCode())
|
assert.Equal(t, 200, m.Ctx.Response.StatusCode())
|
||||||
|
|
Loading…
Reference in New Issue