fix(server): respond with 404/405 appropriately (#3087)

This adjusts the not found handler to not respond with a 404 on not found endpoints that are part of the /api or /.well-known folders, and respond with a 405 when the method isn't implemented.

Co-authored-by: Amir Zarrinkafsh <nightah@me.com>
pull/3108/head^2
James Elliott 2022-04-04 09:58:01 +10:00 committed by GitHub
parent fa143ea029
commit 2502d89682
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 91 additions and 6 deletions

View File

@ -190,3 +190,10 @@ func respondUnauthorized(ctx *middlewares.AutheliaCtx, message string) {
ctx.SetStatusCode(fasthttp.StatusUnauthorized) ctx.SetStatusCode(fasthttp.StatusUnauthorized)
ctx.SetJSONError(message) ctx.SetJSONError(message)
} }
// SetStatusCodeResponse writes a response status code and an appropriate body on either a
// *fasthttp.RequestCtx or *middlewares.AutheliaCtx.
func SetStatusCodeResponse(ctx responseWriter, statusCode int) {
ctx.SetStatusCode(statusCode)
ctx.SetBodyString(fmt.Sprintf("%d %s", statusCode, fasthttp.StatusMessage(statusCode)))
}

View File

@ -1,6 +1,8 @@
package handlers package handlers
import ( import (
"io"
"github.com/authelia/authelia/v4/internal/authentication" "github.com/authelia/authelia/v4/internal/authentication"
) )
@ -124,3 +126,12 @@ type PassworPolicyBody struct {
RequireNumber bool `json:"require_number"` RequireNumber bool `json:"require_number"`
RequireSpecial bool `json:"require_special"` RequireSpecial bool `json:"require_special"`
} }
type responseWriter interface {
SetStatusCode(statusCode int)
SetBodyString(body string)
SetBody(body []byte)
SetContentType(contentType string)
SetContentTypeBytes(contentType []byte)
SetBodyStream(bodyStream io.Reader, bodySize int)
}

View File

@ -8,7 +8,37 @@ const (
logoFile = "logo.png" logoFile = "logo.png"
) )
var rootFiles = []string{"favicon.ico", "manifest.json", "robots.txt"} var (
rootFiles = []string{"favicon.ico", "manifest.json", "robots.txt"}
swaggerFiles = []string{
"favicon-16x16.png",
"favicon-32x32.png",
"index.css",
"oauth2-redirect.html",
"swagger-initializer.js",
"swagger-ui-bundle.js",
"swagger-ui-bundle.js.map",
"swagger-ui-es-bundle-core.js",
"swagger-ui-es-bundle-core.js.map",
"swagger-ui-es-bundle.js",
"swagger-ui-es-bundle.js.map",
"swagger-ui-standalone-preset.js",
"swagger-ui-standalone-preset.js.map",
"swagger-ui.css",
"swagger-ui.css.map",
"swagger-ui.js",
"swagger-ui.js.map",
}
// Directories excluded from the not found handler proceeding to the next() handler.
httpServerDirs = []struct {
name, prefix string
}{
{name: "/api", prefix: "/api/"},
{name: "/.well-known", prefix: "/.well-known/"},
{name: "/static", prefix: "/static/"},
}
)
const ( const (
dev = "dev" dev = "dev"

View File

@ -0,0 +1,25 @@
package server
import (
"strings"
"github.com/valyala/fasthttp"
"github.com/authelia/authelia/v4/internal/handlers"
)
func handleNotFound(next fasthttp.RequestHandler) fasthttp.RequestHandler {
return func(ctx *fasthttp.RequestCtx) {
path := strings.ToLower(string(ctx.Path()))
for i := 0; i < len(httpServerDirs); i++ {
if path == httpServerDirs[i].name || strings.HasPrefix(path, httpServerDirs[i].prefix) {
handlers.SetStatusCodeResponse(ctx, fasthttp.StatusNotFound)
return
}
}
next(ctx)
}
}

View File

@ -49,15 +49,18 @@ func registerRoutes(configuration schema.Configuration, providers middlewares.Pr
r.GET("/", autheliaMiddleware(serveIndexHandler)) r.GET("/", autheliaMiddleware(serveIndexHandler))
r.OPTIONS("/", autheliaMiddleware(handleOPTIONS)) r.OPTIONS("/", autheliaMiddleware(handleOPTIONS))
r.GET("/api/", autheliaMiddleware(serveSwaggerHandler))
r.GET("/api/"+apiFile, autheliaMiddleware(serveSwaggerAPIHandler))
for _, f := range rootFiles { for _, f := range rootFiles {
r.GET("/"+f, middlewares.AssetOverrideMiddleware(configuration.Server.AssetPath, embeddedFS)) r.GET("/"+f, middlewares.AssetOverrideMiddleware(configuration.Server.AssetPath, embeddedFS))
} }
r.GET("/api/", autheliaMiddleware(serveSwaggerHandler))
r.GET("/api/"+apiFile, autheliaMiddleware(serveSwaggerAPIHandler))
for _, file := range swaggerFiles {
r.GET("/api/"+file, embeddedFS)
}
r.GET("/static/{filepath:*}", middlewares.AssetOverrideMiddleware(configuration.Server.AssetPath, embeddedFS)) r.GET("/static/{filepath:*}", middlewares.AssetOverrideMiddleware(configuration.Server.AssetPath, embeddedFS))
r.ANY("/api/{filepath:*}", embeddedFS)
r.GET("/api/health", autheliaMiddleware(handlers.HealthGet)) r.GET("/api/health", autheliaMiddleware(handlers.HealthGet))
r.GET("/api/state", autheliaMiddleware(handlers.StateGet)) r.GET("/api/state", autheliaMiddleware(handlers.StateGet))
@ -155,7 +158,12 @@ func registerRoutes(configuration schema.Configuration, providers middlewares.Pr
r.GET("/debug/vars", expvarhandler.ExpvarHandler) r.GET("/debug/vars", expvarhandler.ExpvarHandler)
} }
r.NotFound = autheliaMiddleware(serveIndexHandler) r.NotFound = handleNotFound(autheliaMiddleware(serveIndexHandler))
r.HandleMethodNotAllowed = true
r.MethodNotAllowed = func(ctx *fasthttp.RequestCtx) {
handlers.SetStatusCodeResponse(ctx, fasthttp.StatusMethodNotAllowed)
}
handler := middlewares.LogRequestMiddleware(r.Handler) handler := middlewares.LogRequestMiddleware(r.Handler)
if configuration.Server.Path != "" { if configuration.Server.Path != "" {

View File

@ -64,6 +64,10 @@ func (s *BackendProtectionScenario) TestInvalidEndpointsReturn404() {
s.AssertRequestStatusCode("POST", fmt.Sprintf("%s/api/not_existing/second", AutheliaBaseURL), 404) s.AssertRequestStatusCode("POST", fmt.Sprintf("%s/api/not_existing/second", AutheliaBaseURL), 404)
} }
func (s *BackendProtectionScenario) TestInvalidEndpointsReturn405() {
s.AssertRequestStatusCode("PUT", fmt.Sprintf("%s/api/configuration", AutheliaBaseURL), 405)
}
func TestRunBackendProtection(t *testing.T) { func TestRunBackendProtection(t *testing.T) {
if testing.Short() { if testing.Short() {
t.Skip("skipping suite test in short mode") t.Skip("skipping suite test in short mode")