[MISC] Fail with an error message when X-Forwarded-* headers are missing (#631)
* Fail with an error message when X-Forwarded-* headers are missing. * Remove useless comments.pull/633/head
parent
2ffbea50af
commit
4643e488db
|
@ -9,5 +9,5 @@ const InternalError = "Internal error."
|
||||||
// UnauthorizedError is the error message sent when the user is not authorized.
|
// UnauthorizedError is the error message sent when the user is not authorized.
|
||||||
const UnauthorizedError = "You're not authorized."
|
const UnauthorizedError = "You're not authorized."
|
||||||
|
|
||||||
var errMissingXForwardedHost = errors.New("Missing header X-Fowarded-Host used to detect target URL")
|
var errMissingXForwardedHost = errors.New("Missing header X-Fowarded-Host")
|
||||||
var errMissingXForwardedProto = errors.New("Missing header X-Fowarded-Proto used to detect target URL")
|
var errMissingXForwardedProto = errors.New("Missing header X-Fowarded-Proto")
|
||||||
|
|
|
@ -24,6 +24,16 @@ var SecondFactorU2FIdentityStart = middlewares.IdentityVerificationStart(middlew
|
||||||
})
|
})
|
||||||
|
|
||||||
func secondFactorU2FIdentityFinish(ctx *middlewares.AutheliaCtx, username string) {
|
func secondFactorU2FIdentityFinish(ctx *middlewares.AutheliaCtx, username string) {
|
||||||
|
if ctx.XForwardedProto() == nil {
|
||||||
|
ctx.Error(errMissingXForwardedProto, operationFailedMessage)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.XForwardedHost() == nil {
|
||||||
|
ctx.Error(errMissingXForwardedHost, operationFailedMessage)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
appID := fmt.Sprintf("%s://%s", ctx.XForwardedProto(), ctx.XForwardedHost())
|
appID := fmt.Sprintf("%s://%s", ctx.XForwardedProto(), ctx.XForwardedHost())
|
||||||
ctx.Logger.Tracef("U2F appID is %s", appID)
|
ctx.Logger.Tracef("U2F appID is %s", appID)
|
||||||
var trustedFacets = []string{appID}
|
var trustedFacets = []string{appID}
|
||||||
|
|
|
@ -0,0 +1,96 @@
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/authelia/authelia/internal/middlewares"
|
||||||
|
"github.com/authelia/authelia/internal/mocks"
|
||||||
|
"github.com/dgrijalva/jwt-go"
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HandlerRegisterU2FStep1Suite struct {
|
||||||
|
suite.Suite
|
||||||
|
|
||||||
|
mock *mocks.MockAutheliaCtx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HandlerRegisterU2FStep1Suite) SetupTest() {
|
||||||
|
s.mock = mocks.NewMockAutheliaCtx(s.T())
|
||||||
|
|
||||||
|
userSession := s.mock.Ctx.GetSession()
|
||||||
|
userSession.Username = "john"
|
||||||
|
s.mock.Ctx.SaveSession(userSession)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HandlerRegisterU2FStep1Suite) TearDownTest() {
|
||||||
|
s.mock.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func createToken(secret string, username string, action string, expiresAt time.Time) string {
|
||||||
|
claims := &middlewares.IdentityVerificationClaim{
|
||||||
|
StandardClaims: jwt.StandardClaims{
|
||||||
|
ExpiresAt: expiresAt.Unix(),
|
||||||
|
Issuer: "Authelia",
|
||||||
|
},
|
||||||
|
Action: action,
|
||||||
|
Username: username,
|
||||||
|
}
|
||||||
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||||
|
ss, _ := token.SignedString([]byte(secret))
|
||||||
|
return ss
|
||||||
|
}
|
||||||
|
|
||||||
|
func newFinishArgs() middlewares.IdentityVerificationFinishArgs {
|
||||||
|
return middlewares.IdentityVerificationFinishArgs{
|
||||||
|
ActionClaim: U2FRegistrationAction,
|
||||||
|
IsTokenUserValidFunc: func(ctx *middlewares.AutheliaCtx, username string) bool { return true },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HandlerRegisterU2FStep1Suite) TestShouldRaiseWhenXForwardedProtoIsMissing() {
|
||||||
|
token := createToken(s.mock.Ctx.Configuration.JWTSecret, "john", U2FRegistrationAction,
|
||||||
|
time.Now().Add(1*time.Minute))
|
||||||
|
s.mock.Ctx.Request.SetBodyString(fmt.Sprintf("{\"token\":\"%s\"}", token))
|
||||||
|
|
||||||
|
s.mock.StorageProviderMock.EXPECT().
|
||||||
|
FindIdentityVerificationToken(gomock.Eq(token)).
|
||||||
|
Return(true, nil)
|
||||||
|
|
||||||
|
s.mock.StorageProviderMock.EXPECT().
|
||||||
|
RemoveIdentityVerificationToken(gomock.Eq(token)).
|
||||||
|
Return(nil)
|
||||||
|
|
||||||
|
SecondFactorU2FIdentityFinish(s.mock.Ctx)
|
||||||
|
|
||||||
|
assert.Equal(s.T(), 200, s.mock.Ctx.Response.StatusCode())
|
||||||
|
assert.Equal(s.T(), "Missing header X-Fowarded-Proto", s.mock.Hook.LastEntry().Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HandlerRegisterU2FStep1Suite) TestShouldRaiseWhenXForwardedHostIsMissing() {
|
||||||
|
s.mock.Ctx.Request.Header.Add("X-Forwarded-Proto", "http")
|
||||||
|
token := createToken(s.mock.Ctx.Configuration.JWTSecret, "john", U2FRegistrationAction,
|
||||||
|
time.Now().Add(1*time.Minute))
|
||||||
|
s.mock.Ctx.Request.SetBodyString(fmt.Sprintf("{\"token\":\"%s\"}", token))
|
||||||
|
|
||||||
|
s.mock.StorageProviderMock.EXPECT().
|
||||||
|
FindIdentityVerificationToken(gomock.Eq(token)).
|
||||||
|
Return(true, nil)
|
||||||
|
|
||||||
|
s.mock.StorageProviderMock.EXPECT().
|
||||||
|
RemoveIdentityVerificationToken(gomock.Eq(token)).
|
||||||
|
Return(nil)
|
||||||
|
|
||||||
|
SecondFactorU2FIdentityFinish(s.mock.Ctx)
|
||||||
|
|
||||||
|
assert.Equal(s.T(), 200, s.mock.Ctx.Response.StatusCode())
|
||||||
|
assert.Equal(s.T(), "Missing header X-Fowarded-Host", s.mock.Hook.LastEntry().Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldRunHandlerRegisterU2FStep1Suite(t *testing.T) {
|
||||||
|
suite.Run(t, new(HandlerRegisterU2FStep1Suite))
|
||||||
|
}
|
|
@ -12,7 +12,15 @@ 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) {
|
||||||
userSession := ctx.GetSession()
|
if ctx.XForwardedProto() == nil {
|
||||||
|
ctx.Error(errMissingXForwardedProto, operationFailedMessage)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.XForwardedHost() == nil {
|
||||||
|
ctx.Error(errMissingXForwardedHost, operationFailedMessage)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
appID := fmt.Sprintf("%s://%s", ctx.XForwardedProto(), ctx.XForwardedHost())
|
appID := fmt.Sprintf("%s://%s", ctx.XForwardedProto(), ctx.XForwardedHost())
|
||||||
var trustedFacets = []string{appID}
|
var trustedFacets = []string{appID}
|
||||||
|
@ -23,6 +31,7 @@ func SecondFactorU2FSignGet(ctx *middlewares.AutheliaCtx) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
userSession := ctx.GetSession()
|
||||||
keyHandleBytes, publicKeyBytes, err := ctx.Providers.StorageProvider.LoadU2FDeviceHandle(userSession.Username)
|
keyHandleBytes, publicKeyBytes, err := ctx.Providers.StorageProvider.LoadU2FDeviceHandle(userSession.Username)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/authelia/authelia/internal/mocks"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HandlerSignU2FStep1Suite struct {
|
||||||
|
suite.Suite
|
||||||
|
|
||||||
|
mock *mocks.MockAutheliaCtx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HandlerSignU2FStep1Suite) SetupTest() {
|
||||||
|
s.mock = mocks.NewMockAutheliaCtx(s.T())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HandlerSignU2FStep1Suite) TearDownTest() {
|
||||||
|
s.mock.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HandlerSignU2FStep1Suite) TestShouldRaiseWhenXForwardedProtoIsMissing() {
|
||||||
|
SecondFactorU2FSignGet(s.mock.Ctx)
|
||||||
|
|
||||||
|
assert.Equal(s.T(), 200, s.mock.Ctx.Response.StatusCode())
|
||||||
|
assert.Equal(s.T(), "Missing header X-Fowarded-Proto", s.mock.Hook.LastEntry().Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *HandlerSignU2FStep1Suite) TestShouldRaiseWhenXForwardedHostIsMissing() {
|
||||||
|
s.mock.Ctx.Request.Header.Add("X-Forwarded-Proto", "http")
|
||||||
|
SecondFactorU2FSignGet(s.mock.Ctx)
|
||||||
|
|
||||||
|
assert.Equal(s.T(), 200, s.mock.Ctx.Response.StatusCode())
|
||||||
|
assert.Equal(s.T(), "Missing header X-Fowarded-Host", s.mock.Hook.LastEntry().Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldRunHandlerSignU2FStep1Suite(t *testing.T) {
|
||||||
|
suite.Run(t, new(HandlerSignU2FStep1Suite))
|
||||||
|
}
|
|
@ -72,7 +72,7 @@ func TestShouldRaiseWhenNoHeaderProvidedToDetectTargetURL(t *testing.T) {
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
_, err := getOriginalURL(mock.Ctx)
|
_, err := getOriginalURL(mock.Ctx)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Equal(t, "Missing header X-Fowarded-Proto used to detect target URL", err.Error())
|
assert.Equal(t, "Missing header X-Fowarded-Proto", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldRaiseWhenNoXForwardedHostHeaderProvidedToDetectTargetURL(t *testing.T) {
|
func TestShouldRaiseWhenNoXForwardedHostHeaderProvidedToDetectTargetURL(t *testing.T) {
|
||||||
|
@ -82,7 +82,7 @@ func TestShouldRaiseWhenNoXForwardedHostHeaderProvidedToDetectTargetURL(t *testi
|
||||||
mock.Ctx.Request.Header.Set("X-Forwarded-Proto", "https")
|
mock.Ctx.Request.Header.Set("X-Forwarded-Proto", "https")
|
||||||
_, err := getOriginalURL(mock.Ctx)
|
_, err := getOriginalURL(mock.Ctx)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Equal(t, "Missing header X-Fowarded-Host used to detect target URL", err.Error())
|
assert.Equal(t, "Missing header X-Fowarded-Host", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldRaiseWhenXForwardedProtoIsNotParseable(t *testing.T) {
|
func TestShouldRaiseWhenXForwardedProtoIsNotParseable(t *testing.T) {
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
package middlewares
|
package middlewares
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
// InternalError is the error message sent when there was an internal error but it should
|
// InternalError is the error message sent when there was an internal error but it should
|
||||||
// be hidden to the end user. In that case the error should be in the server logs.
|
// be hidden to the end user. In that case the error should be in the server logs.
|
||||||
const InternalError = "Internal error."
|
const InternalError = "Internal error."
|
||||||
|
|
||||||
// UnauthorizedError is the error message sent when the user is not authorized.
|
// UnauthorizedError is the error message sent when the user is not authorized.
|
||||||
const UnauthorizedError = "You're not authorized."
|
const UnauthorizedError = "You're not authorized."
|
||||||
|
|
||||||
|
var errMissingXForwardedHost = errors.New("Missing header X-Fowarded-Host")
|
||||||
|
var errMissingXForwardedProto = errors.New("Missing header X-Fowarded-Proto")
|
||||||
|
|
|
@ -49,6 +49,16 @@ func IdentityVerificationStart(args IdentityVerificationStartArgs) RequestHandle
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ctx.XForwardedProto() == nil {
|
||||||
|
ctx.Error(errMissingXForwardedProto, operationFailedMessage)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.XForwardedHost() == nil {
|
||||||
|
ctx.Error(errMissingXForwardedHost, operationFailedMessage)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
link := fmt.Sprintf("%s://%s%s?token=%s", ctx.XForwardedProto(),
|
link := fmt.Sprintf("%s://%s%s?token=%s", ctx.XForwardedProto(),
|
||||||
ctx.XForwardedHost(), args.TargetEndpoint, ss)
|
ctx.XForwardedHost(), args.TargetEndpoint, ss)
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,6 @@ func TestShouldFailStartingProcessIfUserHasNoEmailAddress(t *testing.T) {
|
||||||
|
|
||||||
middlewares.IdentityVerificationStart(newArgs(retriever))(mock.Ctx)
|
middlewares.IdentityVerificationStart(newArgs(retriever))(mock.Ctx)
|
||||||
|
|
||||||
// Return 200 KO
|
|
||||||
assert.Equal(t, 200, mock.Ctx.Response.StatusCode())
|
assert.Equal(t, 200, mock.Ctx.Response.StatusCode())
|
||||||
assert.Equal(t, "User does not have any email", mock.Hook.LastEntry().Message)
|
assert.Equal(t, "User does not have any email", mock.Hook.LastEntry().Message)
|
||||||
}
|
}
|
||||||
|
@ -60,7 +59,6 @@ func TestShouldFailIfJWTCannotBeSaved(t *testing.T) {
|
||||||
args := newArgs(defaultRetriever)
|
args := newArgs(defaultRetriever)
|
||||||
middlewares.IdentityVerificationStart(args)(mock.Ctx)
|
middlewares.IdentityVerificationStart(args)(mock.Ctx)
|
||||||
|
|
||||||
// Return 200 KO
|
|
||||||
assert.Equal(t, 200, mock.Ctx.Response.StatusCode())
|
assert.Equal(t, 200, mock.Ctx.Response.StatusCode())
|
||||||
assert.Equal(t, "cannot save", mock.Hook.LastEntry().Message)
|
assert.Equal(t, "cannot save", mock.Hook.LastEntry().Message)
|
||||||
}
|
}
|
||||||
|
@ -70,6 +68,8 @@ func TestShouldFailSendingAnEmail(t *testing.T) {
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
mock.Ctx.Configuration.JWTSecret = "abc"
|
mock.Ctx.Configuration.JWTSecret = "abc"
|
||||||
|
mock.Ctx.Request.Header.Add("X-Forwarded-Proto", "http")
|
||||||
|
mock.Ctx.Request.Header.Add("X-Forwarded-Host", "host")
|
||||||
|
|
||||||
mock.StorageProviderMock.EXPECT().
|
mock.StorageProviderMock.EXPECT().
|
||||||
SaveIdentityVerificationToken(gomock.Any()).
|
SaveIdentityVerificationToken(gomock.Any()).
|
||||||
|
@ -82,16 +82,53 @@ func TestShouldFailSendingAnEmail(t *testing.T) {
|
||||||
args := newArgs(defaultRetriever)
|
args := newArgs(defaultRetriever)
|
||||||
middlewares.IdentityVerificationStart(args)(mock.Ctx)
|
middlewares.IdentityVerificationStart(args)(mock.Ctx)
|
||||||
|
|
||||||
// Return 200 KO
|
|
||||||
assert.Equal(t, 200, mock.Ctx.Response.StatusCode())
|
assert.Equal(t, 200, mock.Ctx.Response.StatusCode())
|
||||||
assert.Equal(t, "no notif", mock.Hook.LastEntry().Message)
|
assert.Equal(t, "no notif", mock.Hook.LastEntry().Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestShouldFailWhenXForwardedProtoHeaderIsMissing(t *testing.T) {
|
||||||
|
mock := mocks.NewMockAutheliaCtx(t)
|
||||||
|
defer mock.Close()
|
||||||
|
|
||||||
|
mock.Ctx.Configuration.JWTSecret = "abc"
|
||||||
|
mock.Ctx.Request.Header.Add("X-Forwarded-Host", "host")
|
||||||
|
|
||||||
|
mock.StorageProviderMock.EXPECT().
|
||||||
|
SaveIdentityVerificationToken(gomock.Any()).
|
||||||
|
Return(nil)
|
||||||
|
|
||||||
|
args := newArgs(defaultRetriever)
|
||||||
|
middlewares.IdentityVerificationStart(args)(mock.Ctx)
|
||||||
|
|
||||||
|
assert.Equal(t, 200, mock.Ctx.Response.StatusCode())
|
||||||
|
assert.Equal(t, "Missing header X-Fowarded-Proto", mock.Hook.LastEntry().Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShouldFailWhenXForwardedHostHeaderIsMissing(t *testing.T) {
|
||||||
|
mock := mocks.NewMockAutheliaCtx(t)
|
||||||
|
defer mock.Close()
|
||||||
|
|
||||||
|
mock.Ctx.Configuration.JWTSecret = "abc"
|
||||||
|
mock.Ctx.Request.Header.Add("X-Forwarded-Proto", "http")
|
||||||
|
|
||||||
|
mock.StorageProviderMock.EXPECT().
|
||||||
|
SaveIdentityVerificationToken(gomock.Any()).
|
||||||
|
Return(nil)
|
||||||
|
|
||||||
|
args := newArgs(defaultRetriever)
|
||||||
|
middlewares.IdentityVerificationStart(args)(mock.Ctx)
|
||||||
|
|
||||||
|
assert.Equal(t, 200, mock.Ctx.Response.StatusCode())
|
||||||
|
assert.Equal(t, "Missing header X-Fowarded-Host", mock.Hook.LastEntry().Message)
|
||||||
|
}
|
||||||
|
|
||||||
func TestShouldSucceedIdentityVerificationStartProcess(t *testing.T) {
|
func TestShouldSucceedIdentityVerificationStartProcess(t *testing.T) {
|
||||||
mock := mocks.NewMockAutheliaCtx(t)
|
mock := mocks.NewMockAutheliaCtx(t)
|
||||||
defer mock.Close()
|
defer mock.Close()
|
||||||
|
|
||||||
mock.Ctx.Configuration.JWTSecret = "abc"
|
mock.Ctx.Configuration.JWTSecret = "abc"
|
||||||
|
mock.Ctx.Request.Header.Add("X-Forwarded-Proto", "http")
|
||||||
|
mock.Ctx.Request.Header.Add("X-Forwarded-Host", "host")
|
||||||
|
|
||||||
mock.StorageProviderMock.EXPECT().
|
mock.StorageProviderMock.EXPECT().
|
||||||
SaveIdentityVerificationToken(gomock.Any()).
|
SaveIdentityVerificationToken(gomock.Any()).
|
||||||
|
|
Loading…
Reference in New Issue