ServeFromSubPath: Redirect to URL with subpath when subpath missing (#66724)

* ServeFromSubPath: Redirect to URL with subpath when subpath missing

* Review fixes

* Added tests

* Use constant

* change to useMiddleware

* Update pkg/middleware/subpath_redirect.go

---------

Co-authored-by: Carl Bergquist <carl.bergquist@gmail.com>
This commit is contained in:
Torkel Ödegaard 2023-04-24 09:55:55 +02:00 committed by GitHub
parent 2767d5b1c2
commit 57701fd2f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 74 additions and 0 deletions

View File

@ -602,6 +602,7 @@ func (hs *HTTPServer) addMiddlewaresAndStaticRoutes() {
if hs.Cfg.ServeFromSubPath && hs.Cfg.AppSubURL != "" { if hs.Cfg.ServeFromSubPath && hs.Cfg.AppSubURL != "" {
m.SetURLPrefix(hs.Cfg.AppSubURL) m.SetURLPrefix(hs.Cfg.AppSubURL)
m.UseMiddleware(middleware.SubPathRedirect(hs.Cfg))
} }
m.UseMiddleware(web.Renderer(filepath.Join(hs.Cfg.StaticRootPath, "views"), "[[", "]]")) m.UseMiddleware(web.Renderer(filepath.Join(hs.Cfg.StaticRootPath, "views"), "[[", "]]"))
@ -616,6 +617,7 @@ func (hs *HTTPServer) addMiddlewaresAndStaticRoutes() {
m.UseMiddleware(hs.ContextHandler.Middleware) m.UseMiddleware(hs.ContextHandler.Middleware)
m.Use(middleware.OrgRedirect(hs.Cfg, hs.userService)) m.Use(middleware.OrgRedirect(hs.Cfg, hs.userService))
if !hs.Features.IsEnabled(featuremgmt.FlagAuthnService) { if !hs.Features.IsEnabled(featuremgmt.FlagAuthnService) {
m.Use(accesscontrol.LoadPermissionsMiddleware(hs.accesscontrolService)) m.Use(accesscontrol.LoadPermissionsMiddleware(hs.accesscontrolService))
} }

View File

@ -0,0 +1,26 @@
package middleware
import (
"fmt"
"net/http"
"strings"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/web"
)
// SubPathRedirect Redirects URLs that are missing the configured subpath to an URL that contains the subpath.
func SubPathRedirect(cfg *setting.Cfg) web.Middleware {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
// Direct to url with subpath if the request is missing the subpath and is not an API request.
if !strings.HasPrefix(req.RequestURI, cfg.AppSubURL) && !strings.HasPrefix(req.RequestURI, "/api") {
newURL := fmt.Sprintf("%s%s", cfg.AppURL, strings.TrimPrefix(req.RequestURI, "/"))
http.Redirect(rw, req, newURL, http.StatusMovedPermanently)
return
}
next.ServeHTTP(rw, req)
})
}
}

View File

@ -0,0 +1,43 @@
package middleware
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestSubPathRedirect(t *testing.T) {
testSubPathRedirect(t, "/", "http://localhost:3000/subpath/")
testSubPathRedirect(t, "/admin/my/page", "http://localhost:3000/subpath/admin/my/page")
testSubPathRedirect(t, "/api/users", "")
}
func testSubPathRedirect(t *testing.T, url string, expectedRedirect string) {
middlewareScenario(t, "GET url without subpath", func(t *testing.T, sc *scenarioContext) {
sc.cfg.AppSubURL = "/subpath"
sc.cfg.AppURL = "http://localhost:3000/subpath/"
sc.m.UseMiddleware(SubPathRedirect(sc.cfg))
sc.m.Get("/api/users", sc.defaultHandler)
sc.fakeReqWithParams("GET", url, map[string]string{}).exec()
if expectedRedirect != "" {
assert.Equal(t, 301, sc.resp.Code)
// nolint:bodyclose
resp := sc.resp.Result()
t.Cleanup(func() {
err := resp.Body.Close()
assert.NoError(t, err)
})
redirectURL, err := resp.Location()
require.NoError(t, err)
assert.Equal(t, expectedRedirect, redirectURL.String())
} else {
assert.Equal(t, 200, sc.resp.Code)
}
})
}

View File

@ -99,7 +99,10 @@ func (sc *scenarioContext) fakeReqWithParams(method, url string, queryParams map
for k, v := range queryParams { for k, v := range queryParams {
q.Add(k, v) q.Add(k, v)
} }
req.URL.RawQuery = q.Encode() req.URL.RawQuery = q.Encode()
req.RequestURI = req.URL.RequestURI()
require.NoError(sc.t, err) require.NoError(sc.t, err)
reqCtx := &contextmodel.ReqContext{ reqCtx := &contextmodel.ReqContext{