fix(server): use of inconsistent methods for determining origin (#2848)
This unifies the methods to obtain the X-Forwarded-* header values and provides logical fallbacks. In addition, so we can ensure this functionality extends to the templated files we've converted the ServeTemplatedFile method into a function that operates as a middlewares.RequestHandler. Fixes #2765pull/2799/head^2
parent
7775d2af0e
commit
26236f491e
|
@ -48,25 +48,6 @@ func createToken(ctx *mocks.MockAutheliaCtx, username, action string, expiresAt
|
|||
return ss, verification
|
||||
}
|
||||
|
||||
func (s *HandlerRegisterU2FStep1Suite) TestShouldRaiseWhenXForwardedProtoIsMissing() {
|
||||
token, verification := createToken(s.mock, "john", ActionU2FRegistration,
|
||||
time.Now().Add(1*time.Minute))
|
||||
s.mock.Ctx.Request.SetBodyString(fmt.Sprintf("{\"token\":\"%s\"}", token))
|
||||
|
||||
s.mock.StorageMock.EXPECT().
|
||||
FindIdentityVerification(s.mock.Ctx, gomock.Eq(verification.JTI.String())).
|
||||
Return(true, nil)
|
||||
|
||||
s.mock.StorageMock.EXPECT().
|
||||
ConsumeIdentityVerification(s.mock.Ctx, gomock.Eq(verification.JTI.String()), gomock.Eq(models.NewNullIP(s.mock.Ctx.RemoteIP()))).
|
||||
Return(nil)
|
||||
|
||||
SecondFactorU2FIdentityFinish(s.mock.Ctx)
|
||||
|
||||
assert.Equal(s.T(), 200, s.mock.Ctx.Response.StatusCode())
|
||||
assert.Equal(s.T(), "missing header X-Forwarded-Proto", s.mock.Hook.LastEntry().Message)
|
||||
}
|
||||
|
||||
func (s *HandlerRegisterU2FStep1Suite) TestShouldRaiseWhenXForwardedHostIsMissing() {
|
||||
s.mock.Ctx.Request.Header.Add("X-Forwarded-Proto", "http")
|
||||
token, verification := createToken(s.mock, "john", ActionU2FRegistration,
|
||||
|
|
|
@ -27,7 +27,7 @@ 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-Forwarded-Proto", s.mock.Hook.LastEntry().Message)
|
||||
assert.Equal(s.T(), "missing header X-Forwarded-Host", s.mock.Hook.LastEntry().Message)
|
||||
}
|
||||
|
||||
func (s *HandlerSignU2FStep1Suite) TestShouldRaiseWhenXForwardedHostIsMissing() {
|
||||
|
|
|
@ -45,7 +45,7 @@ func TestShouldRaiseWhenNoHeaderProvidedToDetectTargetURL(t *testing.T) {
|
|||
defer mock.Close()
|
||||
_, err := mock.Ctx.GetOriginalURL()
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "Missing header X-Forwarded-Proto", err.Error())
|
||||
assert.Equal(t, "Missing header X-Forwarded-Host", err.Error())
|
||||
}
|
||||
|
||||
func TestShouldRaiseWhenNoXForwardedHostHeaderProvidedToDetectTargetURL(t *testing.T) {
|
||||
|
@ -67,7 +67,7 @@ func TestShouldRaiseWhenXForwardedProtoIsNotParsable(t *testing.T) {
|
|||
|
||||
_, err := mock.Ctx.GetOriginalURL()
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "Unable to parse URL !:;;:,://myhost.local: parse \"!:;;:,://myhost.local\": invalid URI for request", err.Error())
|
||||
assert.Equal(t, "Unable to parse URL !:;;:,://myhost.local/: parse \"!:;;:,://myhost.local/\": invalid URI for request", err.Error())
|
||||
}
|
||||
|
||||
func TestShouldRaiseWhenXForwardedURIIsNotParsable(t *testing.T) {
|
||||
|
|
|
@ -55,75 +55,97 @@ func AutheliaMiddleware(configuration schema.Configuration, providers Providers)
|
|||
}
|
||||
|
||||
// Error reply with an error and display the stack trace in the logs.
|
||||
func (c *AutheliaCtx) Error(err error, message string) {
|
||||
c.SetJSONError(message)
|
||||
func (ctx *AutheliaCtx) Error(err error, message string) {
|
||||
ctx.SetJSONError(message)
|
||||
|
||||
c.Logger.Error(err)
|
||||
ctx.Logger.Error(err)
|
||||
}
|
||||
|
||||
// SetJSONError sets the body of the response to an JSON error KO message.
|
||||
func (c *AutheliaCtx) SetJSONError(message string) {
|
||||
func (ctx *AutheliaCtx) SetJSONError(message string) {
|
||||
b, marshalErr := json.Marshal(ErrorResponse{Status: "KO", Message: message})
|
||||
|
||||
if marshalErr != nil {
|
||||
c.Logger.Error(marshalErr)
|
||||
ctx.Logger.Error(marshalErr)
|
||||
}
|
||||
|
||||
c.SetContentType(contentTypeApplicationJSON)
|
||||
c.SetBody(b)
|
||||
ctx.SetContentType(contentTypeApplicationJSON)
|
||||
ctx.SetBody(b)
|
||||
}
|
||||
|
||||
// ReplyError reply with an error but does not display any stack trace in the logs.
|
||||
func (c *AutheliaCtx) ReplyError(err error, message string) {
|
||||
func (ctx *AutheliaCtx) ReplyError(err error, message string) {
|
||||
b, marshalErr := json.Marshal(ErrorResponse{Status: "KO", Message: message})
|
||||
|
||||
if marshalErr != nil {
|
||||
c.Logger.Error(marshalErr)
|
||||
ctx.Logger.Error(marshalErr)
|
||||
}
|
||||
|
||||
c.SetContentType(contentTypeApplicationJSON)
|
||||
c.SetBody(b)
|
||||
c.Logger.Debug(err)
|
||||
ctx.SetContentType(contentTypeApplicationJSON)
|
||||
ctx.SetBody(b)
|
||||
ctx.Logger.Debug(err)
|
||||
}
|
||||
|
||||
// ReplyUnauthorized response sent when user is unauthorized.
|
||||
func (c *AutheliaCtx) ReplyUnauthorized() {
|
||||
c.RequestCtx.Error(fasthttp.StatusMessage(fasthttp.StatusUnauthorized), fasthttp.StatusUnauthorized)
|
||||
func (ctx *AutheliaCtx) ReplyUnauthorized() {
|
||||
ctx.RequestCtx.Error(fasthttp.StatusMessage(fasthttp.StatusUnauthorized), fasthttp.StatusUnauthorized)
|
||||
}
|
||||
|
||||
// ReplyForbidden response sent when access is forbidden to user.
|
||||
func (c *AutheliaCtx) ReplyForbidden() {
|
||||
c.RequestCtx.Error(fasthttp.StatusMessage(fasthttp.StatusForbidden), fasthttp.StatusForbidden)
|
||||
func (ctx *AutheliaCtx) ReplyForbidden() {
|
||||
ctx.RequestCtx.Error(fasthttp.StatusMessage(fasthttp.StatusForbidden), fasthttp.StatusForbidden)
|
||||
}
|
||||
|
||||
// ReplyBadRequest response sent when bad request has been sent.
|
||||
func (c *AutheliaCtx) ReplyBadRequest() {
|
||||
c.RequestCtx.Error(fasthttp.StatusMessage(fasthttp.StatusBadRequest), fasthttp.StatusBadRequest)
|
||||
func (ctx *AutheliaCtx) ReplyBadRequest() {
|
||||
ctx.RequestCtx.Error(fasthttp.StatusMessage(fasthttp.StatusBadRequest), fasthttp.StatusBadRequest)
|
||||
}
|
||||
|
||||
// XForwardedProto return the content of the X-Forwarded-Proto header.
|
||||
func (c *AutheliaCtx) XForwardedProto() []byte {
|
||||
return c.RequestCtx.Request.Header.PeekBytes(headerXForwardedProto)
|
||||
func (ctx *AutheliaCtx) XForwardedProto() (proto []byte) {
|
||||
proto = ctx.RequestCtx.Request.Header.PeekBytes(headerXForwardedProto)
|
||||
|
||||
if proto == nil {
|
||||
if ctx.RequestCtx.IsTLS() {
|
||||
return protoHTTPS
|
||||
}
|
||||
|
||||
return protoHTTP
|
||||
}
|
||||
|
||||
return proto
|
||||
}
|
||||
|
||||
// XForwardedMethod return the content of the X-Forwarded-Method header.
|
||||
func (c *AutheliaCtx) XForwardedMethod() []byte {
|
||||
return c.RequestCtx.Request.Header.PeekBytes(headerXForwardedMethod)
|
||||
func (ctx *AutheliaCtx) XForwardedMethod() (method []byte) {
|
||||
return ctx.RequestCtx.Request.Header.PeekBytes(headerXForwardedMethod)
|
||||
}
|
||||
|
||||
// XForwardedHost return the content of the X-Forwarded-Host header.
|
||||
func (c *AutheliaCtx) XForwardedHost() []byte {
|
||||
return c.RequestCtx.Request.Header.PeekBytes(headerXForwardedHost)
|
||||
func (ctx *AutheliaCtx) XForwardedHost() (host []byte) {
|
||||
host = ctx.RequestCtx.Request.Header.PeekBytes(headerXForwardedHost)
|
||||
|
||||
if host == nil {
|
||||
return ctx.RequestCtx.Host()
|
||||
}
|
||||
|
||||
return host
|
||||
}
|
||||
|
||||
// XForwardedURI return the content of the X-Forwarded-URI header.
|
||||
func (c *AutheliaCtx) XForwardedURI() []byte {
|
||||
return c.RequestCtx.Request.Header.PeekBytes(headerXForwardedURI)
|
||||
func (ctx *AutheliaCtx) XForwardedURI() (uri []byte) {
|
||||
uri = ctx.RequestCtx.Request.Header.PeekBytes(headerXForwardedURI)
|
||||
|
||||
if len(uri) == 0 {
|
||||
return ctx.RequestCtx.RequestURI()
|
||||
}
|
||||
|
||||
return uri
|
||||
}
|
||||
|
||||
// BasePath returns the base_url as per the path visited by the client.
|
||||
func (c *AutheliaCtx) BasePath() (base string) {
|
||||
if baseURL := c.UserValue("base_url"); baseURL != nil {
|
||||
func (ctx *AutheliaCtx) BasePath() (base string) {
|
||||
if baseURL := ctx.UserValueBytes(UserValueKeyBaseURL); baseURL != nil {
|
||||
return baseURL.(string)
|
||||
}
|
||||
|
||||
|
@ -131,20 +153,20 @@ func (c *AutheliaCtx) BasePath() (base string) {
|
|||
}
|
||||
|
||||
// ExternalRootURL gets the X-Forwarded-Proto, X-Forwarded-Host headers and the BasePath and forms them into a URL.
|
||||
func (c *AutheliaCtx) ExternalRootURL() (string, error) {
|
||||
protocol := c.XForwardedProto()
|
||||
func (ctx *AutheliaCtx) ExternalRootURL() (string, error) {
|
||||
protocol := ctx.XForwardedProto()
|
||||
if protocol == nil {
|
||||
return "", errMissingXForwardedProto
|
||||
}
|
||||
|
||||
host := c.XForwardedHost()
|
||||
host := ctx.XForwardedHost()
|
||||
if host == nil {
|
||||
return "", errMissingXForwardedHost
|
||||
}
|
||||
|
||||
externalRootURL := fmt.Sprintf("%s://%s", protocol, host)
|
||||
|
||||
if base := c.BasePath(); base != "" {
|
||||
if base := ctx.BasePath(); base != "" {
|
||||
externalBaseURL, err := url.Parse(externalRootURL)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
@ -159,15 +181,15 @@ func (c *AutheliaCtx) ExternalRootURL() (string, error) {
|
|||
}
|
||||
|
||||
// XOriginalURL return the content of the X-Original-URL header.
|
||||
func (c *AutheliaCtx) XOriginalURL() []byte {
|
||||
return c.RequestCtx.Request.Header.PeekBytes(headerXOriginalURL)
|
||||
func (ctx *AutheliaCtx) XOriginalURL() []byte {
|
||||
return ctx.RequestCtx.Request.Header.PeekBytes(headerXOriginalURL)
|
||||
}
|
||||
|
||||
// GetSession return the user session. Any update will be saved in cache.
|
||||
func (c *AutheliaCtx) GetSession() session.UserSession {
|
||||
userSession, err := c.Providers.SessionProvider.GetSession(c.RequestCtx)
|
||||
func (ctx *AutheliaCtx) GetSession() session.UserSession {
|
||||
userSession, err := ctx.Providers.SessionProvider.GetSession(ctx.RequestCtx)
|
||||
if err != nil {
|
||||
c.Logger.Error("Unable to retrieve user session")
|
||||
ctx.Logger.Error("Unable to retrieve user session")
|
||||
return session.NewDefaultUserSession()
|
||||
}
|
||||
|
||||
|
@ -175,19 +197,19 @@ func (c *AutheliaCtx) GetSession() session.UserSession {
|
|||
}
|
||||
|
||||
// SaveSession save the content of the session.
|
||||
func (c *AutheliaCtx) SaveSession(userSession session.UserSession) error {
|
||||
return c.Providers.SessionProvider.SaveSession(c.RequestCtx, userSession)
|
||||
func (ctx *AutheliaCtx) SaveSession(userSession session.UserSession) error {
|
||||
return ctx.Providers.SessionProvider.SaveSession(ctx.RequestCtx, userSession)
|
||||
}
|
||||
|
||||
// ReplyOK is a helper method to reply ok.
|
||||
func (c *AutheliaCtx) ReplyOK() {
|
||||
c.SetContentType(contentTypeApplicationJSON)
|
||||
c.SetBody(okMessageBytes)
|
||||
func (ctx *AutheliaCtx) ReplyOK() {
|
||||
ctx.SetContentType(contentTypeApplicationJSON)
|
||||
ctx.SetBody(okMessageBytes)
|
||||
}
|
||||
|
||||
// ParseBody parse the request body into the type of value.
|
||||
func (c *AutheliaCtx) ParseBody(value interface{}) error {
|
||||
err := json.Unmarshal(c.PostBody(), &value)
|
||||
func (ctx *AutheliaCtx) ParseBody(value interface{}) error {
|
||||
err := json.Unmarshal(ctx.PostBody(), &value)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to parse body: %w", err)
|
||||
|
@ -207,21 +229,21 @@ func (c *AutheliaCtx) ParseBody(value interface{}) error {
|
|||
}
|
||||
|
||||
// SetJSONBody Set json body.
|
||||
func (c *AutheliaCtx) SetJSONBody(value interface{}) error {
|
||||
func (ctx *AutheliaCtx) SetJSONBody(value interface{}) error {
|
||||
b, err := json.Marshal(OKResponse{Status: "OK", Data: value})
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to marshal JSON body: %w", err)
|
||||
}
|
||||
|
||||
c.SetContentType(contentTypeApplicationJSON)
|
||||
c.SetBody(b)
|
||||
ctx.SetContentType(contentTypeApplicationJSON)
|
||||
ctx.SetBody(b)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoteIP return the remote IP taking X-Forwarded-For header into account if provided.
|
||||
func (c *AutheliaCtx) RemoteIP() net.IP {
|
||||
XForwardedFor := c.Request.Header.PeekBytes(headerXForwardedFor)
|
||||
func (ctx *AutheliaCtx) RemoteIP() net.IP {
|
||||
XForwardedFor := ctx.Request.Header.PeekBytes(headerXForwardedFor)
|
||||
if XForwardedFor != nil {
|
||||
ips := strings.Split(string(XForwardedFor), ",")
|
||||
|
||||
|
@ -230,26 +252,24 @@ func (c *AutheliaCtx) RemoteIP() net.IP {
|
|||
}
|
||||
}
|
||||
|
||||
return c.RequestCtx.RemoteIP()
|
||||
return ctx.RequestCtx.RemoteIP()
|
||||
}
|
||||
|
||||
// GetOriginalURL extract the URL from the request headers (X-Original-URI or X-Forwarded-* headers).
|
||||
func (c *AutheliaCtx) GetOriginalURL() (*url.URL, error) {
|
||||
originalURL := c.XOriginalURL()
|
||||
// GetOriginalURL extract the URL from the request headers (X-Original-URL or X-Forwarded-* headers).
|
||||
func (ctx *AutheliaCtx) GetOriginalURL() (*url.URL, error) {
|
||||
originalURL := ctx.XOriginalURL()
|
||||
if originalURL != nil {
|
||||
parsedURL, err := url.ParseRequestURI(string(originalURL))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Unable to parse URL extracted from X-Original-URL header: %v", err)
|
||||
}
|
||||
|
||||
c.Logger.Trace("Using X-Original-URL header content as targeted site URL")
|
||||
ctx.Logger.Trace("Using X-Original-URL header content as targeted site URL")
|
||||
|
||||
return parsedURL, nil
|
||||
}
|
||||
|
||||
forwardedProto := c.XForwardedProto()
|
||||
forwardedHost := c.XForwardedHost()
|
||||
forwardedURI := c.XForwardedURI()
|
||||
forwardedProto, forwardedHost, forwardedURI := ctx.XForwardedProto(), ctx.XForwardedHost(), ctx.XForwardedURI()
|
||||
|
||||
if forwardedProto == nil {
|
||||
return nil, errMissingXForwardedProto
|
||||
|
@ -271,22 +291,22 @@ func (c *AutheliaCtx) GetOriginalURL() (*url.URL, error) {
|
|||
return nil, fmt.Errorf("Unable to parse URL %s: %v", requestURI, err)
|
||||
}
|
||||
|
||||
c.Logger.Tracef("Using X-Fowarded-Proto, X-Forwarded-Host and X-Forwarded-URI headers " +
|
||||
ctx.Logger.Tracef("Using X-Fowarded-Proto, X-Forwarded-Host and X-Forwarded-URI headers " +
|
||||
"to construct targeted site URL")
|
||||
|
||||
return parsedURL, nil
|
||||
}
|
||||
|
||||
// IsXHR returns true if the request is a XMLHttpRequest.
|
||||
func (c AutheliaCtx) IsXHR() (xhr bool) {
|
||||
requestedWith := c.Request.Header.PeekBytes(headerXRequestedWith)
|
||||
func (ctx AutheliaCtx) IsXHR() (xhr bool) {
|
||||
requestedWith := ctx.Request.Header.PeekBytes(headerXRequestedWith)
|
||||
|
||||
return requestedWith != nil && string(requestedWith) == headerValueXRequestedWithXHR
|
||||
return requestedWith != nil && strings.EqualFold(string(requestedWith), headerValueXRequestedWithXHR)
|
||||
}
|
||||
|
||||
// AcceptsMIME takes a mime type and returns true if the request accepts that type or the wildcard type.
|
||||
func (c AutheliaCtx) AcceptsMIME(mime string) (acceptsMime bool) {
|
||||
accepts := strings.Split(string(c.Request.Header.PeekBytes(headerAccept)), ",")
|
||||
func (ctx AutheliaCtx) AcceptsMIME(mime string) (acceptsMime bool) {
|
||||
accepts := strings.Split(string(ctx.Request.Header.PeekBytes(headerAccept)), ",")
|
||||
|
||||
for i, accept := range accepts {
|
||||
mimeType := strings.Trim(strings.SplitN(accept, ";", 2)[0], " ")
|
||||
|
@ -300,22 +320,22 @@ func (c AutheliaCtx) AcceptsMIME(mime string) (acceptsMime bool) {
|
|||
|
||||
// SpecialRedirect performs a redirect similar to fasthttp.RequestCtx except it allows statusCode 401 and includes body
|
||||
// content in the form of a link to the location.
|
||||
func (c *AutheliaCtx) SpecialRedirect(uri string, statusCode int) {
|
||||
func (ctx *AutheliaCtx) SpecialRedirect(uri string, statusCode int) {
|
||||
if statusCode < fasthttp.StatusMovedPermanently || (statusCode > fasthttp.StatusSeeOther && statusCode != fasthttp.StatusTemporaryRedirect && statusCode != fasthttp.StatusPermanentRedirect && statusCode != fasthttp.StatusUnauthorized) {
|
||||
statusCode = fasthttp.StatusFound
|
||||
}
|
||||
|
||||
c.SetContentType(contentTypeTextHTML)
|
||||
c.SetStatusCode(statusCode)
|
||||
ctx.SetContentType(contentTypeTextHTML)
|
||||
ctx.SetStatusCode(statusCode)
|
||||
|
||||
u := fasthttp.AcquireURI()
|
||||
|
||||
c.URI().CopyTo(u)
|
||||
ctx.URI().CopyTo(u)
|
||||
u.Update(uri)
|
||||
|
||||
c.Response.Header.SetBytesV("Location", u.FullURI())
|
||||
ctx.Response.Header.SetBytesV("Location", u.FullURI())
|
||||
|
||||
c.SetBodyString(fmt.Sprintf("<a href=\"%s\">%s</a>", utils.StringHTMLEscape(string(u.FullURI())), fasthttp.StatusMessage(statusCode)))
|
||||
ctx.SetBodyString(fmt.Sprintf("<a href=\"%s\">%s</a>", utils.StringHTMLEscape(string(u.FullURI())), fasthttp.StatusMessage(statusCode)))
|
||||
|
||||
fasthttp.ReleaseURI(u)
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ func TestShouldGetOriginalURLFromForwardedHeadersWithoutURI(t *testing.T) {
|
|||
originalURL, err := mock.Ctx.GetOriginalURL()
|
||||
assert.NoError(t, err)
|
||||
|
||||
expectedURL, err := url.ParseRequestURI("https://home.example.com")
|
||||
expectedURL, err := url.ParseRequestURI("https://home.example.com/")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expectedURL, originalURL)
|
||||
}
|
||||
|
@ -70,3 +70,48 @@ func TestShouldGetOriginalURLFromForwardedHeadersWithURI(t *testing.T) {
|
|||
assert.Error(t, err)
|
||||
assert.Equal(t, "Unable to parse URL extracted from X-Original-URL header: parse \"htt-ps//home?-.example.com\": invalid URI for request", err.Error())
|
||||
}
|
||||
|
||||
func TestShouldFallbackToNonXForwardedHeaders(t *testing.T) {
|
||||
mock := mocks.NewMockAutheliaCtx(t)
|
||||
defer mock.Close()
|
||||
|
||||
mock.Ctx.RequestCtx.Request.SetRequestURI("/2fa/one-time-password")
|
||||
mock.Ctx.RequestCtx.Request.SetHost("auth.example.com:1234")
|
||||
|
||||
assert.Equal(t, []byte("http"), mock.Ctx.XForwardedProto())
|
||||
assert.Equal(t, []byte("auth.example.com:1234"), mock.Ctx.XForwardedHost())
|
||||
assert.Equal(t, []byte("/2fa/one-time-password"), mock.Ctx.XForwardedURI())
|
||||
}
|
||||
|
||||
func TestShouldOnlyFallbackToNonXForwardedHeadersWhenNil(t *testing.T) {
|
||||
mock := mocks.NewMockAutheliaCtx(t)
|
||||
defer mock.Close()
|
||||
|
||||
mock.Ctx.RequestCtx.Request.SetRequestURI("/2fa/one-time-password")
|
||||
mock.Ctx.RequestCtx.Request.SetHost("localhost")
|
||||
mock.Ctx.RequestCtx.Request.Header.Set(fasthttp.HeaderXForwardedHost, "auth.example.com:1234")
|
||||
mock.Ctx.RequestCtx.Request.Header.Set("X-Forwarded-URI", "/base/2fa/one-time-password")
|
||||
mock.Ctx.RequestCtx.Request.Header.Set("X-Forwarded-Proto", "https")
|
||||
mock.Ctx.RequestCtx.Request.Header.Set("X-Forwarded-Method", "GET")
|
||||
|
||||
assert.Equal(t, []byte("https"), mock.Ctx.XForwardedProto())
|
||||
assert.Equal(t, []byte("auth.example.com:1234"), mock.Ctx.XForwardedHost())
|
||||
assert.Equal(t, []byte("/base/2fa/one-time-password"), mock.Ctx.XForwardedURI())
|
||||
assert.Equal(t, []byte("GET"), mock.Ctx.XForwardedMethod())
|
||||
}
|
||||
|
||||
func TestShouldDetectXHR(t *testing.T) {
|
||||
mock := mocks.NewMockAutheliaCtx(t)
|
||||
defer mock.Close()
|
||||
|
||||
mock.Ctx.RequestCtx.Request.Header.Set(fasthttp.HeaderXRequestedWith, "XMLHttpRequest")
|
||||
|
||||
assert.True(t, mock.Ctx.IsXHR())
|
||||
}
|
||||
|
||||
func TestShouldDetectNonXHR(t *testing.T) {
|
||||
mock := mocks.NewMockAutheliaCtx(t)
|
||||
defer mock.Close()
|
||||
|
||||
assert.False(t, mock.Ctx.IsXHR())
|
||||
}
|
||||
|
|
|
@ -14,6 +14,12 @@ var (
|
|||
headerXForwardedURI = []byte("X-Forwarded-URI")
|
||||
headerXOriginalURL = []byte("X-Original-URL")
|
||||
headerXForwardedMethod = []byte("X-Forwarded-Method")
|
||||
|
||||
protoHTTPS = []byte("https")
|
||||
protoHTTP = []byte("http")
|
||||
|
||||
// UserValueKeyBaseURL is the User Value key where we store the Base URL.
|
||||
UserValueKeyBaseURL = []byte("base_url")
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -90,24 +90,6 @@ func TestShouldFailSendingAnEmail(t *testing.T) {
|
|||
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 = testJWTSecret
|
||||
mock.Ctx.Request.Header.Add("X-Forwarded-Host", "host")
|
||||
|
||||
mock.StorageMock.EXPECT().
|
||||
SaveIdentityVerification(mock.Ctx, gomock.Any()).
|
||||
Return(nil)
|
||||
|
||||
args := newArgs(defaultRetriever)
|
||||
middlewares.IdentityVerificationStart(args, nil)(mock.Ctx)
|
||||
|
||||
assert.Equal(t, 200, mock.Ctx.Response.StatusCode())
|
||||
assert.Equal(t, "Missing header X-Forwarded-Proto", mock.Hook.LastEntry().Message)
|
||||
}
|
||||
|
||||
func TestShouldFailWhenXForwardedHostHeaderIsMissing(t *testing.T) {
|
||||
mock := mocks.NewMockAutheliaCtx(t)
|
||||
defer mock.Close()
|
||||
|
|
|
@ -12,7 +12,7 @@ func StripPathMiddleware(path string, next fasthttp.RequestHandler) fasthttp.Req
|
|||
uri := ctx.RequestURI()
|
||||
|
||||
if strings.HasPrefix(string(uri), path) {
|
||||
ctx.SetUserValue("base_url", path)
|
||||
ctx.SetUserValueBytes(UserValueKeyBaseURL, path)
|
||||
|
||||
newURI := strings.TrimPrefix(string(uri), path)
|
||||
ctx.Request.SetRequestURI(newURI)
|
||||
|
|
|
@ -46,11 +46,11 @@ func registerRoutes(configuration schema.Configuration, providers middlewares.Pr
|
|||
serveSwaggerAPIHandler := ServeTemplatedFile(swaggerAssets, apiFile, configuration.Server.AssetPath, duoSelfEnrollment, rememberMe, resetPassword, configuration.Session.Name, configuration.Theme, https)
|
||||
|
||||
r := router.New()
|
||||
r.GET("/", serveIndexHandler)
|
||||
r.GET("/", autheliaMiddleware(serveIndexHandler))
|
||||
r.OPTIONS("/", autheliaMiddleware(handleOPTIONS))
|
||||
|
||||
r.GET("/api/", serveSwaggerHandler)
|
||||
r.GET("/api/"+apiFile, serveSwaggerAPIHandler)
|
||||
r.GET("/api/", autheliaMiddleware(serveSwaggerHandler))
|
||||
r.GET("/api/"+apiFile, autheliaMiddleware(serveSwaggerAPIHandler))
|
||||
|
||||
for _, f := range rootFiles {
|
||||
r.GET("/"+f, middlewares.AssetOverrideMiddleware(configuration.Server.AssetPath, embeddedFS))
|
||||
|
@ -148,7 +148,7 @@ func registerRoutes(configuration schema.Configuration, providers middlewares.Pr
|
|||
r.GET("/debug/vars", expvarhandler.ExpvarHandler)
|
||||
}
|
||||
|
||||
r.NotFound = serveIndexHandler
|
||||
r.NotFound = autheliaMiddleware(serveIndexHandler)
|
||||
|
||||
handler := middlewares.LogRequestMiddleware(r.Handler)
|
||||
if configuration.Server.Path != "" {
|
||||
|
|
|
@ -7,16 +7,15 @@ import (
|
|||
"path/filepath"
|
||||
"text/template"
|
||||
|
||||
"github.com/valyala/fasthttp"
|
||||
|
||||
"github.com/authelia/authelia/v4/internal/logging"
|
||||
"github.com/authelia/authelia/v4/internal/middlewares"
|
||||
"github.com/authelia/authelia/v4/internal/utils"
|
||||
)
|
||||
|
||||
// ServeTemplatedFile serves a templated version of a specified file,
|
||||
// this is utilised to pass information between the backend and frontend
|
||||
// and generate a nonce to support a restrictive CSP while using material-ui.
|
||||
func ServeTemplatedFile(publicDir, file, assetPath, duoSelfEnrollment, rememberMe, resetPassword, session, theme string, https bool) fasthttp.RequestHandler {
|
||||
func ServeTemplatedFile(publicDir, file, assetPath, duoSelfEnrollment, rememberMe, resetPassword, session, theme string, https bool) middlewares.RequestHandler {
|
||||
logger := logging.Logger()
|
||||
|
||||
a, err := assets.Open(publicDir + file)
|
||||
|
@ -34,9 +33,9 @@ func ServeTemplatedFile(publicDir, file, assetPath, duoSelfEnrollment, rememberM
|
|||
logger.Fatalf("Unable to parse %s template: %s", file, err)
|
||||
}
|
||||
|
||||
return func(ctx *fasthttp.RequestCtx) {
|
||||
return func(ctx *middlewares.AutheliaCtx) {
|
||||
base := ""
|
||||
if baseURL := ctx.UserValue("base_url"); baseURL != nil {
|
||||
if baseURL := ctx.UserValueBytes(middlewares.UserValueKeyBaseURL); baseURL != nil {
|
||||
base = baseURL.(string)
|
||||
}
|
||||
|
||||
|
@ -51,16 +50,16 @@ func ServeTemplatedFile(publicDir, file, assetPath, duoSelfEnrollment, rememberM
|
|||
var scheme = "https"
|
||||
|
||||
if !https {
|
||||
proto := string(ctx.Request.Header.Peek(fasthttp.HeaderXForwardedProto))
|
||||
proto := string(ctx.XForwardedProto())
|
||||
switch proto {
|
||||
case "":
|
||||
scheme = "http"
|
||||
default:
|
||||
break
|
||||
case "http", "https":
|
||||
scheme = proto
|
||||
}
|
||||
}
|
||||
|
||||
baseURL := scheme + "://" + string(ctx.Request.Host()) + base + "/"
|
||||
baseURL := scheme + "://" + string(ctx.XForwardedHost()) + base + "/"
|
||||
nonce := utils.RandomString(32, utils.AlphaNumericCharacters, true)
|
||||
|
||||
switch extension := filepath.Ext(file); extension {
|
||||
|
@ -81,7 +80,7 @@ func ServeTemplatedFile(publicDir, file, assetPath, duoSelfEnrollment, rememberM
|
|||
|
||||
err := tmpl.Execute(ctx.Response.BodyWriter(), struct{ Base, BaseURL, CSPNonce, DuoSelfEnrollment, LogoOverride, RememberMe, ResetPassword, Session, Theme string }{Base: base, BaseURL: baseURL, CSPNonce: nonce, DuoSelfEnrollment: duoSelfEnrollment, LogoOverride: logoOverride, RememberMe: rememberMe, ResetPassword: resetPassword, Session: session, Theme: theme})
|
||||
if err != nil {
|
||||
ctx.Error("an error occurred", 503)
|
||||
ctx.RequestCtx.Error("an error occurred", 503)
|
||||
logger.Errorf("Unable to execute template: %v", err)
|
||||
|
||||
return
|
||||
|
|
Loading…
Reference in New Issue