Backend: Remove more globals (#29644)

Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>
This commit is contained in:
Arve Knudsen
2020-12-15 19:09:04 +01:00
committed by GitHub
parent aa8fb1ae98
commit dd2d206d99
35 changed files with 526 additions and 484 deletions
+1 -1
View File
@@ -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)
}
+20 -18
View File
@@ -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
}
}
}
+60 -55
View File
@@ -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()
+35 -35
View File
@@ -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)
+2 -2
View File
@@ -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)
}
}
+2 -2
View File
@@ -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
View File
@@ -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 := &quota.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 := &quota.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)
})
})
}
+3 -3
View File
@@ -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
+5 -5
View File
@@ -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")
}
}
+3 -3
View File
@@ -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
}
}