Backend: Remove more globals (#29644)
Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>
This commit is contained in:
@@ -67,5 +67,5 @@ func WriteSessionCookie(ctx *models.ReqContext, cfg *setting.Cfg, value string,
|
||||
maxAge = int(maxLifetime.Seconds())
|
||||
}
|
||||
|
||||
WriteCookie(ctx.Resp, setting.LoginCookieName, url.QueryEscape(value), maxAge, nil)
|
||||
WriteCookie(ctx.Resp, cfg.LoginCookieName, url.QueryEscape(value), maxAge, nil)
|
||||
}
|
||||
|
||||
@@ -7,12 +7,11 @@ import (
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"gopkg.in/macaron.v1"
|
||||
)
|
||||
|
||||
func getDashboardURLBySlug(orgID int64, slug string) (string, error) {
|
||||
// TODO: Drop bus call
|
||||
query := models.GetDashboardQuery{Slug: slug, OrgId: orgID}
|
||||
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
return "", models.ErrDashboardNotFound
|
||||
}
|
||||
@@ -20,7 +19,7 @@ func getDashboardURLBySlug(orgID int64, slug string) (string, error) {
|
||||
return models.GetDashboardUrl(query.Result.Uid, query.Result.Slug), nil
|
||||
}
|
||||
|
||||
func RedirectFromLegacyDashboardURL() macaron.Handler {
|
||||
func RedirectFromLegacyDashboardURL() func(c *models.ReqContext) {
|
||||
return func(c *models.ReqContext) {
|
||||
slug := c.Params("slug")
|
||||
|
||||
@@ -36,47 +35,50 @@ func RedirectFromLegacyDashboardURL() macaron.Handler {
|
||||
|
||||
// In Grafana v7.0 we changed panel edit & view query parameters.
|
||||
// This middleware tries to detect those old url parameters and direct to the new url query params
|
||||
func RedirectFromLegacyPanelEditURL() macaron.Handler {
|
||||
func RedirectFromLegacyPanelEditURL(cfg *setting.Cfg) func(c *models.ReqContext) {
|
||||
return func(c *models.ReqContext) {
|
||||
queryParams := c.Req.URL.Query()
|
||||
|
||||
panelId, hasPanelId := queryParams["panelId"]
|
||||
panelID, hasPanelID := queryParams["panelId"]
|
||||
_, hasFullscreen := queryParams["fullscreen"]
|
||||
_, hasEdit := queryParams["edit"]
|
||||
|
||||
if hasPanelId && hasFullscreen {
|
||||
if hasPanelID && hasFullscreen {
|
||||
delete(queryParams, "panelId")
|
||||
delete(queryParams, "fullscreen")
|
||||
delete(queryParams, "edit")
|
||||
|
||||
if hasEdit {
|
||||
queryParams["editPanel"] = panelId
|
||||
queryParams["editPanel"] = panelID
|
||||
} else {
|
||||
queryParams["viewPanel"] = panelId
|
||||
queryParams["viewPanel"] = panelID
|
||||
}
|
||||
|
||||
newURL := setting.ToAbsUrl(fmt.Sprintf("%s?%s", strings.TrimPrefix(c.Req.URL.Path, "/"), queryParams.Encode()))
|
||||
newURL := fmt.Sprintf("%s%s?%s", cfg.AppURL, strings.TrimPrefix(c.Req.URL.Path, "/"), queryParams.Encode())
|
||||
c.Redirect(newURL, 301)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func RedirectFromLegacyDashboardSoloURL() macaron.Handler {
|
||||
func RedirectFromLegacyDashboardSoloURL(cfg *setting.Cfg) func(c *models.ReqContext) {
|
||||
return func(c *models.ReqContext) {
|
||||
slug := c.Params("slug")
|
||||
renderRequest := c.QueryBool("render")
|
||||
|
||||
if slug != "" {
|
||||
if url, err := getDashboardURLBySlug(c.OrgId, slug); err == nil {
|
||||
if renderRequest && strings.Contains(url, setting.AppSubUrl) {
|
||||
url = strings.Replace(url, setting.AppSubUrl, "", 1)
|
||||
}
|
||||
|
||||
url = strings.Replace(url, "/d/", "/d-solo/", 1)
|
||||
url = fmt.Sprintf("%s?%s", url, c.Req.URL.RawQuery)
|
||||
c.Redirect(url, 301)
|
||||
url, err := getDashboardURLBySlug(c.OrgId, slug)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if renderRequest && strings.Contains(url, cfg.AppSubURL) {
|
||||
url = strings.Replace(url, cfg.AppSubURL, "", 1)
|
||||
}
|
||||
|
||||
url = strings.Replace(url, "/d/", "/d-solo/", 1)
|
||||
url = fmt.Sprintf("%s?%s", url, c.Req.URL.RawQuery)
|
||||
c.Redirect(url, 301)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,74 +7,79 @@ import (
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMiddlewareDashboardRedirect(t *testing.T) {
|
||||
Convey("Given the dashboard redirect middleware", t, func() {
|
||||
bus.ClearBusHandlers()
|
||||
redirectFromLegacyDashboardUrl := RedirectFromLegacyDashboardURL()
|
||||
redirectFromLegacyDashboardSoloUrl := RedirectFromLegacyDashboardSoloURL()
|
||||
bus.ClearBusHandlers()
|
||||
fakeDash := models.NewDashboard("Child dash")
|
||||
fakeDash.Id = 1
|
||||
fakeDash.FolderId = 1
|
||||
fakeDash.HasAcl = false
|
||||
fakeDash.Uid = util.GenerateShortUID()
|
||||
|
||||
fakeDash := models.NewDashboard("Child dash")
|
||||
fakeDash.Id = 1
|
||||
fakeDash.FolderId = 1
|
||||
fakeDash.HasAcl = false
|
||||
fakeDash.Uid = util.GenerateShortUID()
|
||||
middlewareScenario(t, "GET dashboard by legacy url", func(t *testing.T, sc *scenarioContext) {
|
||||
redirectFromLegacyDashboardURL := RedirectFromLegacyDashboardURL()
|
||||
|
||||
middlewareScenario(t, "GET dashboard by legacy url", func(t *testing.T, sc *scenarioContext) {
|
||||
bus.AddHandler("test", func(query *models.GetDashboardQuery) error {
|
||||
query.Result = fakeDash
|
||||
return nil
|
||||
})
|
||||
|
||||
sc.m.Get("/dashboard/db/:slug", redirectFromLegacyDashboardUrl, sc.defaultHandler)
|
||||
|
||||
sc.fakeReqWithParams("GET", "/dashboard/db/dash?orgId=1&panelId=2", map[string]string{}).exec()
|
||||
|
||||
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, models.GetDashboardUrl(fakeDash.Uid, fakeDash.Slug), redirectURL.Path)
|
||||
assert.Equal(t, 2, len(redirectURL.Query()))
|
||||
bus.AddHandler("test", func(query *models.GetDashboardQuery) error {
|
||||
t.Log("Returning fake dashboard")
|
||||
query.Result = fakeDash
|
||||
return nil
|
||||
})
|
||||
|
||||
middlewareScenario(t, "GET dashboard solo by legacy url", func(t *testing.T, sc *scenarioContext) {
|
||||
bus.AddHandler("test", func(query *models.GetDashboardQuery) error {
|
||||
query.Result = fakeDash
|
||||
return nil
|
||||
})
|
||||
sc.handlerFunc = redirectFromLegacyDashboardURL
|
||||
sc.m.Get("/dashboard/db/:slug", sc.defaultHandler)
|
||||
sc.fakeReqWithParams("GET", "/dashboard/db/dash?orgId=1&panelId=2", map[string]string{}).exec()
|
||||
|
||||
sc.m.Get("/dashboard-solo/db/:slug", redirectFromLegacyDashboardSoloUrl, sc.defaultHandler)
|
||||
|
||||
sc.fakeReqWithParams("GET", "/dashboard-solo/db/dash?orgId=1&panelId=2", map[string]string{}).exec()
|
||||
|
||||
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)
|
||||
expectedURL := models.GetDashboardUrl(fakeDash.Uid, fakeDash.Slug)
|
||||
expectedURL = strings.Replace(expectedURL, "/d/", "/d-solo/", 1)
|
||||
assert.Equal(t, expectedURL, redirectURL.Path)
|
||||
assert.Equal(t, 2, len(redirectURL.Query()))
|
||||
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, models.GetDashboardUrl(fakeDash.Uid, fakeDash.Slug), redirectURL.Path)
|
||||
assert.Len(t, redirectURL.Query(), 2)
|
||||
})
|
||||
|
||||
middlewareScenario(t, "GET dashboard by legacy edit url", func(t *testing.T, sc *scenarioContext) {
|
||||
sc.m.Get("/d/:uid/:slug", RedirectFromLegacyPanelEditURL(), sc.defaultHandler)
|
||||
middlewareScenario(t, "GET dashboard solo by legacy url", func(t *testing.T, sc *scenarioContext) {
|
||||
redirectFromLegacyDashboardSoloURL := RedirectFromLegacyDashboardSoloURL(sc.cfg)
|
||||
|
||||
bus.AddHandler("test", func(query *models.GetDashboardQuery) error {
|
||||
t.Log("Returning fake dashboard")
|
||||
query.Result = fakeDash
|
||||
return nil
|
||||
})
|
||||
|
||||
sc.handlerFunc = redirectFromLegacyDashboardSoloURL
|
||||
sc.m.Get("/dashboard-solo/db/:slug", sc.defaultHandler)
|
||||
|
||||
sc.fakeReqWithParams("GET", "/dashboard-solo/db/dash?orgId=1&panelId=2", map[string]string{}).exec()
|
||||
|
||||
require.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)
|
||||
// XXX: Should this be called path??
|
||||
expectedURL := models.GetDashboardUrl(fakeDash.Uid, fakeDash.Slug)
|
||||
expectedURL = strings.Replace(expectedURL, "/d/", "/d-solo/", 1)
|
||||
assert.Equal(t, expectedURL, redirectURL.Path)
|
||||
assert.Len(t, redirectURL.Query(), 2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestMiddlewareDashboardRedirect_legacyEditPanel(t *testing.T) {
|
||||
middlewareScenario(t, "GET dashboard by legacy edit URL", func(t *testing.T, sc *scenarioContext) {
|
||||
sc.handlerFunc = RedirectFromLegacyPanelEditURL(sc.cfg)
|
||||
sc.m.Get("/d/:uid/:slug", sc.defaultHandler)
|
||||
|
||||
sc.fakeReqWithParams("GET", "/d/asd/dash?orgId=1&panelId=12&fullscreen&edit", map[string]string{}).exec()
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/components/gtime"
|
||||
"github.com/grafana/grafana/pkg/infra/fs"
|
||||
"github.com/grafana/grafana/pkg/infra/remotecache"
|
||||
"github.com/grafana/grafana/pkg/login"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
@@ -30,8 +31,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
const errorTemplate = "error-template"
|
||||
|
||||
func fakeGetTime() func() time.Time {
|
||||
var timeSeed int64
|
||||
return func() time.Time {
|
||||
@@ -42,12 +41,6 @@ func fakeGetTime() func() time.Time {
|
||||
}
|
||||
|
||||
func TestMiddleWareSecurityHeaders(t *testing.T) {
|
||||
origErrTemplateName := setting.ErrTemplateName
|
||||
t.Cleanup(func() {
|
||||
setting.ErrTemplateName = origErrTemplateName
|
||||
})
|
||||
setting.ErrTemplateName = errorTemplate
|
||||
|
||||
middlewareScenario(t, "middleware should get correct x-xss-protection header", func(t *testing.T, sc *scenarioContext) {
|
||||
sc.fakeReq("GET", "/api/").exec()
|
||||
assert.Equal(t, "1; mode=block", sc.resp.Header().Get("X-XSS-Protection"))
|
||||
@@ -79,11 +72,7 @@ func TestMiddleWareSecurityHeaders(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMiddlewareContext(t *testing.T) {
|
||||
origErrTemplateName := setting.ErrTemplateName
|
||||
t.Cleanup(func() {
|
||||
setting.ErrTemplateName = origErrTemplateName
|
||||
})
|
||||
setting.ErrTemplateName = errorTemplate
|
||||
const noCache = "no-cache"
|
||||
|
||||
middlewareScenario(t, "middleware should add context to injector", func(t *testing.T, sc *scenarioContext) {
|
||||
sc.fakeReq("GET", "/").exec()
|
||||
@@ -97,8 +86,8 @@ func TestMiddlewareContext(t *testing.T) {
|
||||
|
||||
middlewareScenario(t, "middleware should add Cache-Control header for requests to API", func(t *testing.T, sc *scenarioContext) {
|
||||
sc.fakeReq("GET", "/api/search").exec()
|
||||
assert.Equal(t, "no-cache", sc.resp.Header().Get("Cache-Control"))
|
||||
assert.Equal(t, "no-cache", sc.resp.Header().Get("Pragma"))
|
||||
assert.Equal(t, noCache, sc.resp.Header().Get("Cache-Control"))
|
||||
assert.Equal(t, noCache, sc.resp.Header().Get("Pragma"))
|
||||
assert.Equal(t, "-1", sc.resp.Header().Get("Expires"))
|
||||
})
|
||||
|
||||
@@ -110,20 +99,23 @@ func TestMiddlewareContext(t *testing.T) {
|
||||
assert.Empty(t, sc.resp.Header().Get("Expires"))
|
||||
})
|
||||
|
||||
middlewareScenario(t, "middleware should add Cache-Control header for requests with html response", func(
|
||||
middlewareScenario(t, "middleware should add Cache-Control header for requests with HTML response", func(
|
||||
t *testing.T, sc *scenarioContext) {
|
||||
sc.handler(func(c *models.ReqContext) {
|
||||
sc.handlerFunc = func(c *models.ReqContext) {
|
||||
t.Log("Handler called")
|
||||
data := &dtos.IndexViewData{
|
||||
User: &dtos.CurrentUser{},
|
||||
Settings: map[string]interface{}{},
|
||||
NavTree: []*dtos.NavLink{},
|
||||
}
|
||||
t.Log("Calling HTML", "data", data, "render", c.Render)
|
||||
c.HTML(200, "index-template", data)
|
||||
})
|
||||
t.Log("Returned HTML with code 200")
|
||||
}
|
||||
sc.fakeReq("GET", "/").exec()
|
||||
assert.Equal(t, 200, sc.resp.Code)
|
||||
assert.Equal(t, "no-cache", sc.resp.Header().Get("Cache-Control"))
|
||||
assert.Equal(t, "no-cache", sc.resp.Header().Get("Pragma"))
|
||||
require.Equal(t, 200, sc.resp.Code)
|
||||
assert.Equal(t, noCache, sc.resp.Header().Get("Cache-Control"))
|
||||
assert.Equal(t, noCache, sc.resp.Header().Get("Pragma"))
|
||||
assert.Equal(t, "-1", sc.resp.Header().Get("Expires"))
|
||||
})
|
||||
|
||||
@@ -150,7 +142,7 @@ func TestMiddlewareContext(t *testing.T) {
|
||||
assert.Equal(t, contexthandler.InvalidAPIKey, sc.respJson["message"])
|
||||
})
|
||||
|
||||
middlewareScenario(t, "Valid api key", func(t *testing.T, sc *scenarioContext) {
|
||||
middlewareScenario(t, "Valid API key", func(t *testing.T, sc *scenarioContext) {
|
||||
const orgID int64 = 12
|
||||
keyhash, err := util.EncodePassword("v5nAwpMafFP6znaS4urhdWDLS5511M42", "asd")
|
||||
require.NoError(t, err)
|
||||
@@ -162,15 +154,15 @@ func TestMiddlewareContext(t *testing.T) {
|
||||
|
||||
sc.fakeReq("GET", "/").withValidApiKey().exec()
|
||||
|
||||
assert.Equal(t, 200, sc.resp.Code)
|
||||
require.Equal(t, 200, sc.resp.Code)
|
||||
|
||||
assert.True(t, sc.context.IsSignedIn)
|
||||
assert.Equal(t, orgID, sc.context.OrgId)
|
||||
assert.Equal(t, models.ROLE_EDITOR, sc.context.OrgRole)
|
||||
})
|
||||
|
||||
middlewareScenario(t, "Valid api key, but does not match db hash", func(t *testing.T, sc *scenarioContext) {
|
||||
keyhash := "Something_not_matching"
|
||||
middlewareScenario(t, "Valid API key, but does not match DB hash", func(t *testing.T, sc *scenarioContext) {
|
||||
const keyhash = "Something_not_matching"
|
||||
|
||||
bus.AddHandler("test", func(query *models.GetApiKeyByNameQuery) error {
|
||||
query.Result = &models.ApiKey{OrgId: 12, Role: models.ROLE_EDITOR, Key: keyhash}
|
||||
@@ -223,14 +215,16 @@ func TestMiddlewareContext(t *testing.T) {
|
||||
|
||||
sc.fakeReq("GET", "/").exec()
|
||||
|
||||
require.NotNil(t, sc.context)
|
||||
require.NotNil(t, sc.context.UserToken)
|
||||
assert.True(t, sc.context.IsSignedIn)
|
||||
assert.Equal(t, userID, sc.context.UserId)
|
||||
assert.Equal(t, userID, sc.context.UserToken.UserId)
|
||||
assert.Equal(t, "token", sc.context.UserToken.UnhashedToken)
|
||||
assert.Equal(t, "", sc.resp.Header().Get("Set-Cookie"))
|
||||
assert.Empty(t, sc.resp.Header().Get("Set-Cookie"))
|
||||
})
|
||||
|
||||
middlewareScenario(t, "Non-expired auth token in cookie which are being rotated", func(t *testing.T, sc *scenarioContext) {
|
||||
middlewareScenario(t, "Non-expired auth token in cookie which is being rotated", func(t *testing.T, sc *scenarioContext) {
|
||||
const userID int64 = 12
|
||||
|
||||
sc.withTokenSessionCookie("token")
|
||||
@@ -253,7 +247,7 @@ func TestMiddlewareContext(t *testing.T) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
maxAge := int(setting.LoginMaxLifetime.Seconds())
|
||||
maxAge := int(sc.cfg.LoginMaxLifetime.Seconds())
|
||||
|
||||
sameSiteModes := []http.SameSite{
|
||||
http.SameSiteNoneMode,
|
||||
@@ -269,11 +263,11 @@ func TestMiddlewareContext(t *testing.T) {
|
||||
setting.CookieSameSiteMode = sameSiteMode
|
||||
|
||||
expectedCookiePath := "/"
|
||||
if len(setting.AppSubUrl) > 0 {
|
||||
expectedCookiePath = setting.AppSubUrl
|
||||
if len(sc.cfg.AppSubURL) > 0 {
|
||||
expectedCookiePath = sc.cfg.AppSubURL
|
||||
}
|
||||
expectedCookie := &http.Cookie{
|
||||
Name: setting.LoginCookieName,
|
||||
Name: sc.cfg.LoginCookieName,
|
||||
Value: "rotated",
|
||||
Path: expectedCookiePath,
|
||||
HttpOnly: true,
|
||||
@@ -303,11 +297,11 @@ func TestMiddlewareContext(t *testing.T) {
|
||||
setting.CookieSameSiteMode = http.SameSiteLaxMode
|
||||
|
||||
expectedCookiePath := "/"
|
||||
if len(setting.AppSubUrl) > 0 {
|
||||
expectedCookiePath = setting.AppSubUrl
|
||||
if len(sc.cfg.AppSubURL) > 0 {
|
||||
expectedCookiePath = sc.cfg.AppSubURL
|
||||
}
|
||||
expectedCookie := &http.Cookie{
|
||||
Name: setting.LoginCookieName,
|
||||
Name: sc.cfg.LoginCookieName,
|
||||
Value: "rotated",
|
||||
Path: expectedCookiePath,
|
||||
HttpOnly: true,
|
||||
@@ -556,6 +550,8 @@ func middlewareScenario(t *testing.T, desc string, fn scenarioFunc, cbs ...func(
|
||||
cfg := setting.NewCfg()
|
||||
cfg.LoginCookieName = "grafana_session"
|
||||
cfg.LoginMaxLifetime = loginMaxLifetime
|
||||
// Required when rendering errors
|
||||
cfg.ErrTemplateName = "error-template"
|
||||
for _, cb := range cbs {
|
||||
cb(cfg)
|
||||
}
|
||||
@@ -564,6 +560,9 @@ func middlewareScenario(t *testing.T, desc string, fn scenarioFunc, cbs ...func(
|
||||
|
||||
viewsPath, err := filepath.Abs("../../public/views")
|
||||
require.NoError(t, err)
|
||||
exists, err := fs.Exists(viewsPath)
|
||||
require.NoError(t, err)
|
||||
require.Truef(t, exists, "Views directory should exist at %q", viewsPath)
|
||||
|
||||
sc.m = macaron.New()
|
||||
sc.m.Use(AddDefaultResponseHeaders(cfg))
|
||||
@@ -575,7 +574,7 @@ func middlewareScenario(t *testing.T, desc string, fn scenarioFunc, cbs ...func(
|
||||
ctxHdlr := getContextHandler(t, cfg)
|
||||
sc.contextHandler = ctxHdlr
|
||||
sc.m.Use(ctxHdlr.Middleware)
|
||||
sc.m.Use(OrgRedirect())
|
||||
sc.m.Use(OrgRedirect(sc.cfg))
|
||||
|
||||
sc.userAuthTokenService = ctxHdlr.AuthTokenService.(*auth.FakeUserAuthTokenService)
|
||||
sc.remoteCacheService = ctxHdlr.RemoteCache
|
||||
@@ -587,6 +586,7 @@ func middlewareScenario(t *testing.T, desc string, fn scenarioFunc, cbs ...func(
|
||||
if sc.handlerFunc != nil {
|
||||
sc.handlerFunc(sc.context)
|
||||
} else {
|
||||
t.Log("Returning JSON OK")
|
||||
resp := make(map[string]interface{})
|
||||
resp["message"] = "OK"
|
||||
c.JSON(200, resp)
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
|
||||
// OrgRedirect changes org and redirects users if the
|
||||
// querystring `orgId` doesn't match the active org.
|
||||
func OrgRedirect() macaron.Handler {
|
||||
func OrgRedirect(cfg *setting.Cfg) macaron.Handler {
|
||||
return func(res http.ResponseWriter, req *http.Request, c *macaron.Context) {
|
||||
orgIdValue := req.URL.Query().Get("orgId")
|
||||
orgId, err := strconv.ParseInt(orgIdValue, 10, 64)
|
||||
@@ -43,7 +43,7 @@ func OrgRedirect() macaron.Handler {
|
||||
return
|
||||
}
|
||||
|
||||
newURL := setting.ToAbsUrl(fmt.Sprintf("%s?%s", strings.TrimPrefix(c.Req.URL.Path, "/"), c.Req.URL.Query().Encode()))
|
||||
newURL := fmt.Sprintf("%s%s?%s", cfg.AppURL, strings.TrimPrefix(c.Req.URL.Path, "/"), c.Req.URL.Query().Encode())
|
||||
c.Redirect(newURL, 302)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,13 +10,13 @@ import (
|
||||
)
|
||||
|
||||
// Quota returns a function that returns a function used to call quotaservice based on target name
|
||||
func Quota(quotaService *quota.QuotaService) func(target string) macaron.Handler {
|
||||
func Quota(quotaService *quota.QuotaService) func(string) macaron.Handler {
|
||||
//https://open.spotify.com/track/7bZSoBEAEEUsGEuLOf94Jm?si=T1Tdju5qRSmmR0zph_6RBw fuuuuunky
|
||||
return func(target string) macaron.Handler {
|
||||
return func(c *models.ReqContext) {
|
||||
limitReached, err := quotaService.QuotaReached(c, target)
|
||||
if err != nil {
|
||||
c.JsonApiErr(500, "failed to get quota", err)
|
||||
c.JsonApiErr(500, "Failed to get quota", err)
|
||||
return
|
||||
}
|
||||
if limitReached {
|
||||
|
||||
+211
-164
@@ -10,11 +10,220 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/quota"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/stretchr/testify/assert"
|
||||
macaron "gopkg.in/macaron.v1"
|
||||
)
|
||||
|
||||
func TestMiddlewareQuota(t *testing.T) {
|
||||
setting.AnonymousEnabled = false
|
||||
setting.Quota = setting.QuotaSettings{
|
||||
t.Run("With user not logged in", func(t *testing.T) {
|
||||
middlewareScenario(t, "and global quota not reached", func(t *testing.T, sc *scenarioContext) {
|
||||
bus.AddHandler("globalQuota", func(query *models.GetGlobalQuotaByTargetQuery) error {
|
||||
query.Result = &models.GlobalQuotaDTO{
|
||||
Target: query.Target,
|
||||
Limit: query.Default,
|
||||
Used: 4,
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
quotaHandler := getQuotaHandler(sc, "user")
|
||||
|
||||
sc.m.Get("/user", quotaHandler, sc.defaultHandler)
|
||||
sc.fakeReq("GET", "/user").exec()
|
||||
assert.Equal(t, 200, sc.resp.Code)
|
||||
}, configure)
|
||||
|
||||
middlewareScenario(t, "and global quota reached", func(t *testing.T, sc *scenarioContext) {
|
||||
bus.AddHandler("globalQuota", func(query *models.GetGlobalQuotaByTargetQuery) error {
|
||||
query.Result = &models.GlobalQuotaDTO{
|
||||
Target: query.Target,
|
||||
Limit: query.Default,
|
||||
Used: 4,
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
quotaHandler := getQuotaHandler(sc, "user")
|
||||
sc.m.Get("/user", quotaHandler, sc.defaultHandler)
|
||||
sc.fakeReq("GET", "/user").exec()
|
||||
assert.Equal(t, 403, sc.resp.Code)
|
||||
}, func(cfg *setting.Cfg) {
|
||||
configure(cfg)
|
||||
|
||||
cfg.Quota.Global.User = 4
|
||||
})
|
||||
|
||||
middlewareScenario(t, "and global session quota not reached", func(t *testing.T, sc *scenarioContext) {
|
||||
bus.AddHandler("globalQuota", func(query *models.GetGlobalQuotaByTargetQuery) error {
|
||||
query.Result = &models.GlobalQuotaDTO{
|
||||
Target: query.Target,
|
||||
Limit: query.Default,
|
||||
Used: 4,
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
quotaHandler := getQuotaHandler(sc, "session")
|
||||
sc.m.Get("/user", quotaHandler, sc.defaultHandler)
|
||||
sc.fakeReq("GET", "/user").exec()
|
||||
assert.Equal(t, 200, sc.resp.Code)
|
||||
}, func(cfg *setting.Cfg) {
|
||||
configure(cfg)
|
||||
|
||||
cfg.Quota.Global.Session = 10
|
||||
})
|
||||
|
||||
middlewareScenario(t, "and global session quota reached", func(t *testing.T, sc *scenarioContext) {
|
||||
quotaHandler := getQuotaHandler(sc, "session")
|
||||
sc.m.Get("/user", quotaHandler, sc.defaultHandler)
|
||||
sc.fakeReq("GET", "/user").exec()
|
||||
assert.Equal(t, 403, sc.resp.Code)
|
||||
}, func(cfg *setting.Cfg) {
|
||||
configure(cfg)
|
||||
|
||||
cfg.Quota.Global.Session = 1
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("with user logged in", func(t *testing.T) {
|
||||
const quotaUsed = 4
|
||||
|
||||
setUp := func(sc *scenarioContext) {
|
||||
sc.withTokenSessionCookie("token")
|
||||
bus.AddHandler("test", func(query *models.GetSignedInUserQuery) error {
|
||||
query.Result = &models.SignedInUser{OrgId: 2, UserId: 12}
|
||||
return nil
|
||||
})
|
||||
|
||||
sc.userAuthTokenService.LookupTokenProvider = func(ctx context.Context, unhashedToken string) (*models.UserToken, error) {
|
||||
return &models.UserToken{
|
||||
UserId: 12,
|
||||
UnhashedToken: "",
|
||||
}, nil
|
||||
}
|
||||
|
||||
bus.AddHandler("globalQuota", func(query *models.GetGlobalQuotaByTargetQuery) error {
|
||||
query.Result = &models.GlobalQuotaDTO{
|
||||
Target: query.Target,
|
||||
Limit: query.Default,
|
||||
Used: quotaUsed,
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
bus.AddHandler("userQuota", func(query *models.GetUserQuotaByTargetQuery) error {
|
||||
query.Result = &models.UserQuotaDTO{
|
||||
Target: query.Target,
|
||||
Limit: query.Default,
|
||||
Used: quotaUsed,
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
bus.AddHandler("orgQuota", func(query *models.GetOrgQuotaByTargetQuery) error {
|
||||
query.Result = &models.OrgQuotaDTO{
|
||||
Target: query.Target,
|
||||
Limit: query.Default,
|
||||
Used: quotaUsed,
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
middlewareScenario(t, "global datasource quota reached", func(t *testing.T, sc *scenarioContext) {
|
||||
setUp(sc)
|
||||
|
||||
quotaHandler := getQuotaHandler(sc, "data_source")
|
||||
sc.m.Get("/ds", quotaHandler, sc.defaultHandler)
|
||||
sc.fakeReq("GET", "/ds").exec()
|
||||
assert.Equal(t, 403, sc.resp.Code)
|
||||
}, func(cfg *setting.Cfg) {
|
||||
configure(cfg)
|
||||
|
||||
cfg.Quota.Global.DataSource = quotaUsed
|
||||
})
|
||||
|
||||
middlewareScenario(t, "user Org quota not reached", func(t *testing.T, sc *scenarioContext) {
|
||||
setUp(sc)
|
||||
|
||||
quotaHandler := getQuotaHandler(sc, "org")
|
||||
|
||||
sc.m.Get("/org", quotaHandler, sc.defaultHandler)
|
||||
sc.fakeReq("GET", "/org").exec()
|
||||
assert.Equal(t, 200, sc.resp.Code)
|
||||
}, func(cfg *setting.Cfg) {
|
||||
configure(cfg)
|
||||
|
||||
cfg.Quota.User.Org = quotaUsed + 1
|
||||
})
|
||||
|
||||
middlewareScenario(t, "user Org quota reached", func(t *testing.T, sc *scenarioContext) {
|
||||
setUp(sc)
|
||||
|
||||
quotaHandler := getQuotaHandler(sc, "org")
|
||||
sc.m.Get("/org", quotaHandler, sc.defaultHandler)
|
||||
sc.fakeReq("GET", "/org").exec()
|
||||
assert.Equal(t, 403, sc.resp.Code)
|
||||
}, func(cfg *setting.Cfg) {
|
||||
configure(cfg)
|
||||
|
||||
cfg.Quota.User.Org = quotaUsed
|
||||
})
|
||||
|
||||
middlewareScenario(t, "org dashboard quota not reached", func(t *testing.T, sc *scenarioContext) {
|
||||
setUp(sc)
|
||||
|
||||
quotaHandler := getQuotaHandler(sc, "dashboard")
|
||||
sc.m.Get("/dashboard", quotaHandler, sc.defaultHandler)
|
||||
sc.fakeReq("GET", "/dashboard").exec()
|
||||
assert.Equal(t, 200, sc.resp.Code)
|
||||
}, func(cfg *setting.Cfg) {
|
||||
configure(cfg)
|
||||
|
||||
cfg.Quota.Org.Dashboard = quotaUsed + 1
|
||||
})
|
||||
|
||||
middlewareScenario(t, "org dashboard quota reached", func(t *testing.T, sc *scenarioContext) {
|
||||
setUp(sc)
|
||||
|
||||
quotaHandler := getQuotaHandler(sc, "dashboard")
|
||||
sc.m.Get("/dashboard", quotaHandler, sc.defaultHandler)
|
||||
sc.fakeReq("GET", "/dashboard").exec()
|
||||
assert.Equal(t, 403, sc.resp.Code)
|
||||
}, func(cfg *setting.Cfg) {
|
||||
configure(cfg)
|
||||
|
||||
cfg.Quota.Org.Dashboard = quotaUsed
|
||||
})
|
||||
|
||||
middlewareScenario(t, "org dashboard quota reached, but quotas disabled", func(t *testing.T, sc *scenarioContext) {
|
||||
setUp(sc)
|
||||
|
||||
quotaHandler := getQuotaHandler(sc, "dashboard")
|
||||
sc.m.Get("/dashboard", quotaHandler, sc.defaultHandler)
|
||||
sc.fakeReq("GET", "/dashboard").exec()
|
||||
assert.Equal(t, 200, sc.resp.Code)
|
||||
}, func(cfg *setting.Cfg) {
|
||||
configure(cfg)
|
||||
|
||||
cfg.Quota.Org.Dashboard = quotaUsed
|
||||
cfg.Quota.Enabled = false
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func getQuotaHandler(sc *scenarioContext, target string) macaron.Handler {
|
||||
fakeAuthTokenService := auth.NewFakeUserAuthTokenService()
|
||||
qs := "a.QuotaService{
|
||||
AuthTokenService: fakeAuthTokenService,
|
||||
Cfg: sc.cfg,
|
||||
}
|
||||
|
||||
return Quota(qs)(target)
|
||||
}
|
||||
|
||||
func configure(cfg *setting.Cfg) {
|
||||
cfg.AnonymousEnabled = false
|
||||
cfg.Quota = setting.QuotaSettings{
|
||||
Enabled: true,
|
||||
Org: &setting.OrgQuota{
|
||||
User: 5,
|
||||
@@ -34,166 +243,4 @@ func TestMiddlewareQuota(t *testing.T) {
|
||||
Session: 5,
|
||||
},
|
||||
}
|
||||
|
||||
fakeAuthTokenService := auth.NewFakeUserAuthTokenService()
|
||||
qs := "a.QuotaService{
|
||||
AuthTokenService: fakeAuthTokenService,
|
||||
}
|
||||
quotaFn := Quota(qs)
|
||||
|
||||
t.Run("With user not logged in", func(t *testing.T) {
|
||||
middlewareScenario(t, "and global quota not reached", func(t *testing.T, sc *scenarioContext) {
|
||||
bus.AddHandler("globalQuota", func(query *models.GetGlobalQuotaByTargetQuery) error {
|
||||
query.Result = &models.GlobalQuotaDTO{
|
||||
Target: query.Target,
|
||||
Limit: query.Default,
|
||||
Used: 4,
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
sc.m.Get("/user", quotaFn("user"), sc.defaultHandler)
|
||||
sc.fakeReq("GET", "/user").exec()
|
||||
assert.Equal(t, 200, sc.resp.Code)
|
||||
})
|
||||
|
||||
middlewareScenario(t, "and global quota reached", func(t *testing.T, sc *scenarioContext) {
|
||||
bus.AddHandler("globalQuota", func(query *models.GetGlobalQuotaByTargetQuery) error {
|
||||
query.Result = &models.GlobalQuotaDTO{
|
||||
Target: query.Target,
|
||||
Limit: query.Default,
|
||||
Used: 4,
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
origUser := setting.Quota.Global.User
|
||||
t.Cleanup(func() {
|
||||
setting.Quota.Global.User = origUser
|
||||
})
|
||||
setting.Quota.Global.User = 4
|
||||
|
||||
sc.m.Get("/user", quotaFn("user"), sc.defaultHandler)
|
||||
sc.fakeReq("GET", "/user").exec()
|
||||
assert.Equal(t, 403, sc.resp.Code)
|
||||
})
|
||||
|
||||
middlewareScenario(t, "and global session quota not reached", func(t *testing.T, sc *scenarioContext) {
|
||||
bus.AddHandler("globalQuota", func(query *models.GetGlobalQuotaByTargetQuery) error {
|
||||
query.Result = &models.GlobalQuotaDTO{
|
||||
Target: query.Target,
|
||||
Limit: query.Default,
|
||||
Used: 4,
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
origSession := setting.Quota.Global.Session
|
||||
t.Cleanup(func() {
|
||||
setting.Quota.Global.Session = origSession
|
||||
})
|
||||
setting.Quota.Global.Session = 10
|
||||
|
||||
sc.m.Get("/user", quotaFn("session"), sc.defaultHandler)
|
||||
sc.fakeReq("GET", "/user").exec()
|
||||
assert.Equal(t, 200, sc.resp.Code)
|
||||
})
|
||||
|
||||
middlewareScenario(t, "and global session quota reached", func(t *testing.T, sc *scenarioContext) {
|
||||
origSession := setting.Quota.Global.Session
|
||||
t.Cleanup(func() {
|
||||
setting.Quota.Global.Session = origSession
|
||||
})
|
||||
setting.Quota.Global.Session = 1
|
||||
|
||||
sc.m.Get("/user", quotaFn("session"), sc.defaultHandler)
|
||||
sc.fakeReq("GET", "/user").exec()
|
||||
assert.Equal(t, 403, sc.resp.Code)
|
||||
})
|
||||
})
|
||||
|
||||
middlewareScenario(t, "with user logged in", func(t *testing.T, sc *scenarioContext) {
|
||||
sc.withTokenSessionCookie("token")
|
||||
bus.AddHandler("test", func(query *models.GetSignedInUserQuery) error {
|
||||
query.Result = &models.SignedInUser{OrgId: 2, UserId: 12}
|
||||
return nil
|
||||
})
|
||||
|
||||
sc.userAuthTokenService.LookupTokenProvider = func(ctx context.Context, unhashedToken string) (*models.UserToken, error) {
|
||||
return &models.UserToken{
|
||||
UserId: 12,
|
||||
UnhashedToken: "",
|
||||
}, nil
|
||||
}
|
||||
|
||||
bus.AddHandler("globalQuota", func(query *models.GetGlobalQuotaByTargetQuery) error {
|
||||
query.Result = &models.GlobalQuotaDTO{
|
||||
Target: query.Target,
|
||||
Limit: query.Default,
|
||||
Used: 4,
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
bus.AddHandler("userQuota", func(query *models.GetUserQuotaByTargetQuery) error {
|
||||
query.Result = &models.UserQuotaDTO{
|
||||
Target: query.Target,
|
||||
Limit: query.Default,
|
||||
Used: 4,
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
bus.AddHandler("orgQuota", func(query *models.GetOrgQuotaByTargetQuery) error {
|
||||
query.Result = &models.OrgQuotaDTO{
|
||||
Target: query.Target,
|
||||
Limit: query.Default,
|
||||
Used: 4,
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
t.Run("global datasource quota reached", func(t *testing.T) {
|
||||
setting.Quota.Global.DataSource = 4
|
||||
sc.m.Get("/ds", quotaFn("data_source"), sc.defaultHandler)
|
||||
sc.fakeReq("GET", "/ds").exec()
|
||||
assert.Equal(t, 403, sc.resp.Code)
|
||||
})
|
||||
|
||||
t.Run("user Org quota not reached", func(t *testing.T) {
|
||||
setting.Quota.User.Org = 5
|
||||
sc.m.Get("/org", quotaFn("org"), sc.defaultHandler)
|
||||
sc.fakeReq("GET", "/org").exec()
|
||||
assert.Equal(t, 200, sc.resp.Code)
|
||||
})
|
||||
|
||||
t.Run("user Org quota reached", func(t *testing.T) {
|
||||
setting.Quota.User.Org = 4
|
||||
sc.m.Get("/org", quotaFn("org"), sc.defaultHandler)
|
||||
sc.fakeReq("GET", "/org").exec()
|
||||
assert.Equal(t, 403, sc.resp.Code)
|
||||
})
|
||||
|
||||
t.Run("org dashboard quota not reached", func(t *testing.T) {
|
||||
setting.Quota.Org.Dashboard = 10
|
||||
sc.m.Get("/dashboard", quotaFn("dashboard"), sc.defaultHandler)
|
||||
sc.fakeReq("GET", "/dashboard").exec()
|
||||
assert.Equal(t, 200, sc.resp.Code)
|
||||
})
|
||||
|
||||
t.Run("org dashboard quota reached", func(t *testing.T) {
|
||||
setting.Quota.Org.Dashboard = 4
|
||||
sc.m.Get("/dashboard", quotaFn("dashboard"), sc.defaultHandler)
|
||||
sc.fakeReq("GET", "/dashboard").exec()
|
||||
assert.Equal(t, 403, sc.resp.Code)
|
||||
})
|
||||
|
||||
t.Run("org dashboard quota reached but quotas disabled", func(t *testing.T) {
|
||||
setting.Quota.Org.Dashboard = 4
|
||||
setting.Quota.Enabled = false
|
||||
sc.m.Get("/dashboard", quotaFn("dashboard"), sc.defaultHandler)
|
||||
sc.fakeReq("GET", "/dashboard").exec()
|
||||
assert.Equal(t, 200, sc.resp.Code)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
func TestRecoveryMiddleware(t *testing.T) {
|
||||
t.Run("Given an API route that panics", func(t *testing.T) {
|
||||
apiURL := "/api/whatever"
|
||||
recoveryScenario(t, "recovery middleware should return json", apiURL, func(t *testing.T, sc *scenarioContext) {
|
||||
recoveryScenario(t, "recovery middleware should return JSON", apiURL, func(t *testing.T, sc *scenarioContext) {
|
||||
sc.handlerFunc = panicHandler
|
||||
sc.fakeReq("GET", apiURL).exec()
|
||||
sc.req.Header.Set("content-type", "application/json")
|
||||
@@ -37,7 +37,7 @@ func TestRecoveryMiddleware(t *testing.T) {
|
||||
|
||||
assert.Equal(t, 500, sc.resp.Code)
|
||||
assert.Equal(t, "text/html; charset=UTF-8", sc.resp.Header().Get("content-type"))
|
||||
assert.True(t, strings.Contains(sc.resp.Body.String(), "<title>Grafana - Error</title>"))
|
||||
assert.Contains(t, sc.resp.Body.String(), "<title>Grafana - Error</title>")
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -76,7 +76,7 @@ func recoveryScenario(t *testing.T, desc string, url string, fn scenarioFunc) {
|
||||
contextHandler := getContextHandler(t, nil)
|
||||
sc.m.Use(contextHandler.Middleware)
|
||||
// mock out gc goroutine
|
||||
sc.m.Use(OrgRedirect())
|
||||
sc.m.Use(OrgRedirect(cfg))
|
||||
|
||||
sc.defaultHandler = func(c *models.ReqContext) {
|
||||
sc.context = c
|
||||
|
||||
@@ -67,6 +67,8 @@ func (sc *scenarioContext) fakeReqWithParams(method, url string, queryParams map
|
||||
|
||||
sc.resp = httptest.NewRecorder()
|
||||
req, err := http.NewRequest(method, url, nil)
|
||||
require.NoError(sc.t, err)
|
||||
|
||||
q := req.URL.Query()
|
||||
for k, v := range queryParams {
|
||||
q.Add(k, v)
|
||||
@@ -78,11 +80,6 @@ func (sc *scenarioContext) fakeReqWithParams(method, url string, queryParams map
|
||||
return sc
|
||||
}
|
||||
|
||||
func (sc *scenarioContext) handler(fn handlerFunc) *scenarioContext {
|
||||
sc.handlerFunc = fn
|
||||
return sc
|
||||
}
|
||||
|
||||
func (sc *scenarioContext) exec() {
|
||||
sc.t.Helper()
|
||||
|
||||
@@ -109,6 +106,9 @@ func (sc *scenarioContext) exec() {
|
||||
if sc.resp.Header().Get("Content-Type") == "application/json; charset=UTF-8" {
|
||||
err := json.NewDecoder(sc.resp.Body).Decode(&sc.respJson)
|
||||
require.NoError(sc.t, err)
|
||||
sc.t.Log("Decoded JSON", "json", sc.respJson)
|
||||
} else {
|
||||
sc.t.Log("Not decoding JSON")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"gopkg.in/macaron.v1"
|
||||
)
|
||||
|
||||
func ValidateHostHeader(domain string) macaron.Handler {
|
||||
func ValidateHostHeader(cfg *setting.Cfg) macaron.Handler {
|
||||
return func(c *models.ReqContext) {
|
||||
// ignore local render calls
|
||||
if c.IsRenderCall {
|
||||
@@ -20,8 +20,8 @@ func ValidateHostHeader(domain string) macaron.Handler {
|
||||
h = h[:i]
|
||||
}
|
||||
|
||||
if !strings.EqualFold(h, domain) {
|
||||
c.Redirect(strings.TrimSuffix(setting.AppUrl, "/")+c.Req.RequestURI, 301)
|
||||
if !strings.EqualFold(h, cfg.Domain) {
|
||||
c.Redirect(strings.TrimSuffix(cfg.AppURL, "/")+c.Req.RequestURI, 301)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user