Chore: use remote cache instead of session storage (#16114)

Replaces session storage in auth_proxy middleware with remote cache

Fixes #15161
This commit is contained in:
Oleg Gaidarenko 2019-04-08 14:31:46 +03:00 committed by GitHub
parent 645a6e5856
commit 67cbc7d4cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 283 additions and 554 deletions

View File

@ -2,7 +2,7 @@
Any ldif files added to the prepopulate subdirectory will be automatically imported into the OpenLdap database. Any ldif files added to the prepopulate subdirectory will be automatically imported into the OpenLdap database.
The ldif files add three users, `ldapviewer`, `ldapeditor` and `ldapadmin`. Two groups, `admins` and `users`, are added that correspond with the group mappings in the default conf/ldap.toml. `ldapadmin` is a member of `admins` and `ldapeditor` is a member of `users`. The ldif files add eight users, `ldap-admin`, `ldap-editor`, `ldap-viewer`, `ldap-carl`, `ldap-daniel`, `ldap-leo`, `ldap-tobias` and `ldap-torkel`. Two groups, `admins` and `users`, are added that correspond with the group mappings in the default conf/ldap.toml. `ldap-admin` is a member of `admins` and `ldap-editor` is a member of `users`.
Note that users that are added here need to specify a `memberOf` attribute manually as well as the `member` attribute for the group. The `memberOf` module usually does this automatically (if you add a group in Apache Directory Studio for example) but this does not work in the entrypoint script as it uses the `slapadd` command to add entries before the server has started and before the `memberOf` module is loaded. Note that users that are added here need to specify a `memberOf` attribute manually as well as the `member` attribute for the group. The `memberOf` module usually does this automatically (if you add a group in Apache Directory Studio for example) but this does not work in the entrypoint script as it uses the `slapadd` command to add entries before the server has started and before the `memberOf` module is loaded.

View File

@ -9,9 +9,8 @@ import (
"github.com/grafana/grafana/pkg/middleware" "github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models" m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/auth" "github.com/grafana/grafana/pkg/services/auth"
"gopkg.in/macaron.v1"
. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
"gopkg.in/macaron.v1"
) )
func loggedInUserScenario(desc string, url string, fn scenarioFunc) { func loggedInUserScenario(desc string, url string, fn scenarioFunc) {
@ -124,7 +123,7 @@ func setupScenarioContext(url string) *scenarioContext {
Delims: macaron.Delims{Left: "[[", Right: "]]"}, Delims: macaron.Delims{Left: "[[", Right: "]]"},
})) }))
sc.m.Use(middleware.GetContextHandler(nil)) sc.m.Use(middleware.GetContextHandler(nil, nil))
return sc return sc
} }

View File

@ -16,6 +16,7 @@ import (
httpstatic "github.com/grafana/grafana/pkg/api/static" httpstatic "github.com/grafana/grafana/pkg/api/static"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/remotecache"
"github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/middleware" "github.com/grafana/grafana/pkg/middleware"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
@ -26,7 +27,6 @@ import (
"github.com/grafana/grafana/pkg/services/hooks" "github.com/grafana/grafana/pkg/services/hooks"
"github.com/grafana/grafana/pkg/services/quota" "github.com/grafana/grafana/pkg/services/quota"
"github.com/grafana/grafana/pkg/services/rendering" "github.com/grafana/grafana/pkg/services/rendering"
"github.com/grafana/grafana/pkg/services/session"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/client_golang/prometheus/promhttp"
@ -48,15 +48,16 @@ type HTTPServer struct {
streamManager *live.StreamManager streamManager *live.StreamManager
httpSrv *http.Server httpSrv *http.Server
RouteRegister routing.RouteRegister `inject:""` RouteRegister routing.RouteRegister `inject:""`
Bus bus.Bus `inject:""` Bus bus.Bus `inject:""`
RenderService rendering.Service `inject:""` RenderService rendering.Service `inject:""`
Cfg *setting.Cfg `inject:""` Cfg *setting.Cfg `inject:""`
HooksService *hooks.HooksService `inject:""` HooksService *hooks.HooksService `inject:""`
CacheService *cache.CacheService `inject:""` CacheService *cache.CacheService `inject:""`
DatasourceCache datasources.CacheService `inject:""` DatasourceCache datasources.CacheService `inject:""`
AuthTokenService models.UserTokenService `inject:""` AuthTokenService models.UserTokenService `inject:""`
QuotaService *quota.QuotaService `inject:""` QuotaService *quota.QuotaService `inject:""`
RemoteCacheService *remotecache.RemoteCache `inject:""`
} }
func (hs *HTTPServer) Init() error { func (hs *HTTPServer) Init() error {
@ -66,8 +67,6 @@ func (hs *HTTPServer) Init() error {
hs.macaron = hs.newMacaron() hs.macaron = hs.newMacaron()
hs.registerRoutes() hs.registerRoutes()
session.Init(&setting.SessionOptions, setting.SessionConnMaxLifetime)
return nil return nil
} }
@ -226,7 +225,10 @@ func (hs *HTTPServer) addMiddlewaresAndStaticRoutes() {
m.Use(hs.healthHandler) m.Use(hs.healthHandler)
m.Use(hs.metricsEndpoint) m.Use(hs.metricsEndpoint)
m.Use(middleware.GetContextHandler(hs.AuthTokenService)) m.Use(middleware.GetContextHandler(
hs.AuthTokenService,
hs.RemoteCacheService,
))
m.Use(middleware.OrgRedirect()) m.Use(middleware.OrgRedirect())
// needs to be after context handler // needs to be after context handler

View File

@ -7,12 +7,10 @@ import (
"errors" "errors"
"time" "time"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting"
) )
var ( var (

View File

@ -5,7 +5,6 @@ import (
"time" "time"
"github.com/bmizerany/assert" "github.com/bmizerany/assert"
"github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
) )

View File

@ -0,0 +1,34 @@
package remotecache
import (
"testing"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/setting"
)
// NewFakeStore creates store for testing
func NewFakeStore(t *testing.T) *RemoteCache {
t.Helper()
opts := &setting.RemoteCacheOptions{
Name: "database",
ConnStr: "",
}
SQLStore := sqlstore.InitTestDB(t)
dc := &RemoteCache{
SQLStore: SQLStore,
Cfg: &setting.Cfg{
RemoteCacheOptions: opts,
},
}
err := dc.Init()
if err != nil {
t.Fatalf("failed to init remote cache for test. error: %v", err)
}
return dc
}

View File

@ -9,18 +9,19 @@ import (
"time" "time"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/infra/remotecache"
"github.com/grafana/grafana/pkg/login" "github.com/grafana/grafana/pkg/login"
m "github.com/grafana/grafana/pkg/models" m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/session"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
) )
var ( const (
AUTH_PROXY_SESSION_VAR = "authProxyHeaderValue"
// cachePrefix is a prefix for the cache key
cachePrefix = "auth-proxy-sync-ttl:%s"
) )
func initContextWithAuthProxy(ctx *m.ReqContext, orgID int64) bool { func initContextWithAuthProxy(store *remotecache.RemoteCache, ctx *m.ReqContext, orgID int64) bool {
if !setting.AuthProxyEnabled { if !setting.AuthProxyEnabled {
return false return false
} }
@ -36,46 +37,17 @@ func initContextWithAuthProxy(ctx *m.ReqContext, orgID int64) bool {
return true return true
} }
// initialize session
if err := ctx.Session.Start(ctx.Context); err != nil {
log.Error(3, "Failed to start session. error %v", err)
return false
}
defer func() {
if err := ctx.Session.Release(); err != nil {
ctx.Logger.Error("failed to save session data", "error", err)
}
}()
query := &m.GetSignedInUserQuery{OrgId: orgID} query := &m.GetSignedInUserQuery{OrgId: orgID}
cacheKey := fmt.Sprintf(cachePrefix, proxyHeaderValue)
userID, err := store.Get(cacheKey)
inCache := err == nil
// if this session has already been authenticated by authProxy just load the user // load the user if we have them
sessProxyValue := ctx.Session.Get(AUTH_PROXY_SESSION_VAR) if inCache {
if sessProxyValue != nil && sessProxyValue.(string) == proxyHeaderValue && getRequestUserId(ctx) > 0 { query.UserId = userID.(int64)
// if we're using ldap, sync user periodically
if setting.LdapEnabled {
syncQuery := &m.LoginUserQuery{
ReqContext: ctx,
Username: proxyHeaderValue,
}
if err := syncGrafanaUserWithLdapUser(syncQuery); err != nil {
if err == login.ErrInvalidCredentials {
ctx.Handle(500, "Unable to authenticate user", err)
return false
}
ctx.Handle(500, "Failed to sync user", err)
return false
}
}
query.UserId = getRequestUserId(ctx)
// if we're using ldap, pass authproxy login name to ldap user sync // if we're using ldap, pass authproxy login name to ldap user sync
} else if setting.LdapEnabled { } else if setting.LdapEnabled {
ctx.Session.Delete(session.SESS_KEY_LASTLDAPSYNC)
syncQuery := &m.LoginUserQuery{ syncQuery := &m.LoginUserQuery{
ReqContext: ctx, ReqContext: ctx,
Username: proxyHeaderValue, Username: proxyHeaderValue,
@ -86,9 +58,6 @@ func initContextWithAuthProxy(ctx *m.ReqContext, orgID int64) bool {
ctx.Handle(500, "Unable to authenticate user", err) ctx.Handle(500, "Unable to authenticate user", err)
return false return false
} }
ctx.Handle(500, "Failed to sync user", err)
return false
} }
if syncQuery.User == nil { if syncQuery.User == nil {
@ -149,67 +118,40 @@ func initContextWithAuthProxy(ctx *m.ReqContext, orgID int64) bool {
ctx.Handle(500, "Failed to find user", err) ctx.Handle(500, "Failed to find user", err)
return true return true
} }
// Make sure that we cannot share a session between different users!
if getRequestUserId(ctx) > 0 && getRequestUserId(ctx) != query.Result.UserId {
// remove session
if err := ctx.Session.Destory(ctx.Context); err != nil {
log.Error(3, "Failed to destroy session. error: %v", err)
}
// initialize a new session
if err := ctx.Session.Start(ctx.Context); err != nil {
log.Error(3, "Failed to start session. error: %v", err)
}
}
ctx.Session.Set(AUTH_PROXY_SESSION_VAR, proxyHeaderValue)
ctx.SignedInUser = query.Result ctx.SignedInUser = query.Result
ctx.IsSignedIn = true ctx.IsSignedIn = true
ctx.Session.Set(session.SESS_KEY_USERID, ctx.UserId)
expiration := time.Duration(-setting.AuthProxyLdapSyncTtl) * time.Minute
value := query.UserId
// This <if> is here to make sure we do not
// rewrite the expiration all the time
if inCache == false {
if err = store.Set(cacheKey, value, expiration); err != nil {
ctx.Handle(500, "Couldn't write a user in cache key", err)
return true
}
}
return true return true
} }
var syncGrafanaUserWithLdapUser = func(query *m.LoginUserQuery) error { var syncGrafanaUserWithLdapUser = func(query *m.LoginUserQuery) error {
expireEpoch := time.Now().Add(time.Duration(-setting.AuthProxyLdapSyncTtl) * time.Minute).Unix() ldapCfg := login.LdapCfg
if len(ldapCfg.Servers) < 1 {
var lastLdapSync int64 return fmt.Errorf("No LDAP servers available")
if lastLdapSyncInSession := query.ReqContext.Session.Get(session.SESS_KEY_LASTLDAPSYNC); lastLdapSyncInSession != nil {
lastLdapSync = lastLdapSyncInSession.(int64)
} }
if lastLdapSync < expireEpoch { for _, server := range ldapCfg.Servers {
ldapCfg := login.LdapCfg author := login.NewLdapAuthenticator(server)
if err := author.SyncUser(query); err != nil {
if len(ldapCfg.Servers) < 1 { return err
return fmt.Errorf("No LDAP servers available")
} }
for _, server := range ldapCfg.Servers {
author := login.NewLdapAuthenticator(server)
if err := author.SyncUser(query); err != nil {
return err
}
}
query.ReqContext.Session.Set(session.SESS_KEY_LASTLDAPSYNC, time.Now().Unix())
} }
return nil return nil
} }
func getRequestUserId(c *m.ReqContext) int64 {
userID := c.Session.Get(session.SESS_KEY_USERID)
if userID != nil {
return userID.(int64)
}
return 0
}
func checkAuthenticationProxy(remoteAddr string, proxyHeaderValue string) error { func checkAuthenticationProxy(remoteAddr string, proxyHeaderValue string) error {
if len(strings.TrimSpace(setting.AuthProxyWhitelist)) == 0 { if len(strings.TrimSpace(setting.AuthProxyWhitelist)) == 0 {
return nil return nil

View File

@ -1,145 +0,0 @@
package middleware
import (
"testing"
"time"
"github.com/grafana/grafana/pkg/login"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/session"
"github.com/grafana/grafana/pkg/setting"
. "github.com/smartystreets/goconvey/convey"
"gopkg.in/macaron.v1"
)
func TestAuthProxyWithLdapEnabled(t *testing.T) {
Convey("When calling sync grafana user with ldap user", t, func() {
setting.LdapEnabled = true
setting.AuthProxyLdapSyncTtl = 60
servers := []*login.LdapServerConf{{Host: "127.0.0.1"}}
login.LdapCfg = login.LdapConfig{Servers: servers}
mockLdapAuther := mockLdapAuthenticator{}
login.NewLdapAuthenticator = func(server *login.LdapServerConf) login.ILdapAuther {
return &mockLdapAuther
}
Convey("When user logs in, call SyncUser", func() {
// arrange
sess := newMockSession()
ctx := m.ReqContext{Session: &sess}
So(sess.Get(session.SESS_KEY_LASTLDAPSYNC), ShouldBeNil)
// act
syncGrafanaUserWithLdapUser(&m.LoginUserQuery{
ReqContext: &ctx,
Username: "test",
})
// assert
So(mockLdapAuther.syncUserCalled, ShouldBeTrue)
So(sess.Get(session.SESS_KEY_LASTLDAPSYNC), ShouldBeGreaterThan, 0)
})
Convey("When session variable not expired, don't sync and don't change session var", func() {
// arrange
sess := newMockSession()
ctx := m.ReqContext{Session: &sess}
now := time.Now().Unix()
sess.Set(session.SESS_KEY_LASTLDAPSYNC, now)
sess.Set(AUTH_PROXY_SESSION_VAR, "test")
// act
syncGrafanaUserWithLdapUser(&m.LoginUserQuery{
ReqContext: &ctx,
Username: "test",
})
// assert
So(sess.Get(session.SESS_KEY_LASTLDAPSYNC), ShouldEqual, now)
So(mockLdapAuther.syncUserCalled, ShouldBeFalse)
})
Convey("When lastldapsync is expired, session variable should be updated", func() {
// arrange
sess := newMockSession()
ctx := m.ReqContext{Session: &sess}
expiredTime := time.Now().Add(time.Duration(-120) * time.Minute).Unix()
sess.Set(session.SESS_KEY_LASTLDAPSYNC, expiredTime)
sess.Set(AUTH_PROXY_SESSION_VAR, "test")
// act
syncGrafanaUserWithLdapUser(&m.LoginUserQuery{
ReqContext: &ctx,
Username: "test",
})
// assert
So(sess.Get(session.SESS_KEY_LASTLDAPSYNC), ShouldBeGreaterThan, expiredTime)
So(mockLdapAuther.syncUserCalled, ShouldBeTrue)
})
})
}
type mockSession struct {
value map[interface{}]interface{}
}
func newMockSession() mockSession {
session := mockSession{}
session.value = make(map[interface{}]interface{})
return session
}
func (s *mockSession) Start(c *macaron.Context) error {
return nil
}
func (s *mockSession) Set(k interface{}, v interface{}) error {
s.value[k] = v
return nil
}
func (s *mockSession) Get(k interface{}) interface{} {
return s.value[k]
}
func (s *mockSession) Delete(k interface{}) interface{} {
delete(s.value, k)
return nil
}
func (s *mockSession) ID() string {
return ""
}
func (s *mockSession) Release() error {
return nil
}
func (s *mockSession) Destory(c *macaron.Context) error {
return nil
}
func (s *mockSession) RegenerateId(c *macaron.Context) error {
return nil
}
type mockLdapAuthenticator struct {
syncUserCalled bool
}
func (a *mockLdapAuthenticator) Login(query *m.LoginUserQuery) error {
return nil
}
func (a *mockLdapAuthenticator) SyncUser(query *m.LoginUserQuery) error {
a.syncUserCalled = true
return nil
}
func (a *mockLdapAuthenticator) GetGrafanaUserFor(ctx *m.ReqContext, ldapUser *login.LdapUserInfo) (*m.User, error) {
return nil, nil
}

View File

@ -11,7 +11,7 @@ func TestMiddlewareAuth(t *testing.T) {
Convey("Given the grafana middleware", t, func() { Convey("Given the grafana middleware", t, func() {
reqSignIn := Auth(&AuthOptions{ReqSignedIn: true}) reqSignIn := Auth(&AuthOptions{ReqSignedIn: true})
middlewareScenario("ReqSignIn true and unauthenticated request", func(sc *scenarioContext) { middlewareScenario(t, "ReqSignIn true and unauthenticated request", func(sc *scenarioContext) {
sc.m.Get("/secure", reqSignIn, sc.defaultHandler) sc.m.Get("/secure", reqSignIn, sc.defaultHandler)
sc.fakeReq("GET", "/secure").exec() sc.fakeReq("GET", "/secure").exec()
@ -21,7 +21,7 @@ func TestMiddlewareAuth(t *testing.T) {
}) })
}) })
middlewareScenario("ReqSignIn true and unauthenticated API request", func(sc *scenarioContext) { middlewareScenario(t, "ReqSignIn true and unauthenticated API request", func(sc *scenarioContext) {
sc.m.Get("/api/secure", reqSignIn, sc.defaultHandler) sc.m.Get("/api/secure", reqSignIn, sc.defaultHandler)
sc.fakeReq("GET", "/api/secure").exec() sc.fakeReq("GET", "/api/secure").exec()

View File

@ -27,7 +27,7 @@ func TestMiddlewareDashboardRedirect(t *testing.T) {
return nil return nil
}) })
middlewareScenario("GET dashboard by legacy url", func(sc *scenarioContext) { middlewareScenario(t, "GET dashboard by legacy url", func(sc *scenarioContext) {
sc.m.Get("/dashboard/db/:slug", redirectFromLegacyDashboardUrl, sc.defaultHandler) sc.m.Get("/dashboard/db/:slug", redirectFromLegacyDashboardUrl, sc.defaultHandler)
sc.fakeReqWithParams("GET", "/dashboard/db/dash?orgId=1&panelId=2", map[string]string{}).exec() sc.fakeReqWithParams("GET", "/dashboard/db/dash?orgId=1&panelId=2", map[string]string{}).exec()
@ -40,7 +40,7 @@ func TestMiddlewareDashboardRedirect(t *testing.T) {
}) })
}) })
middlewareScenario("GET dashboard solo by legacy url", func(sc *scenarioContext) { middlewareScenario(t, "GET dashboard solo by legacy url", func(sc *scenarioContext) {
sc.m.Get("/dashboard-solo/db/:slug", redirectFromLegacyDashboardSoloUrl, sc.defaultHandler) 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() sc.fakeReqWithParams("GET", "/dashboard-solo/db/dash?orgId=1&panelId=2", map[string]string{}).exec()

View File

@ -8,9 +8,9 @@ import (
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/apikeygen" "github.com/grafana/grafana/pkg/components/apikeygen"
"github.com/grafana/grafana/pkg/infra/remotecache"
"github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/log"
m "github.com/grafana/grafana/pkg/models" m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/session"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
macaron "gopkg.in/macaron.v1" macaron "gopkg.in/macaron.v1"
@ -23,12 +23,11 @@ var (
ReqOrgAdmin = RoleAuth(m.ROLE_ADMIN) ReqOrgAdmin = RoleAuth(m.ROLE_ADMIN)
) )
func GetContextHandler(ats m.UserTokenService) macaron.Handler { func GetContextHandler(ats m.UserTokenService, remoteCache *remotecache.RemoteCache) macaron.Handler {
return func(c *macaron.Context) { return func(c *macaron.Context) {
ctx := &m.ReqContext{ ctx := &m.ReqContext{
Context: c, Context: c,
SignedInUser: &m.SignedInUser{}, SignedInUser: &m.SignedInUser{},
Session: session.GetSession(), // should only be used by auth_proxy
IsSignedIn: false, IsSignedIn: false,
AllowAnonymous: false, AllowAnonymous: false,
SkipCache: false, SkipCache: false,
@ -50,7 +49,7 @@ func GetContextHandler(ats m.UserTokenService) macaron.Handler {
case initContextWithRenderAuth(ctx): case initContextWithRenderAuth(ctx):
case initContextWithApiKey(ctx): case initContextWithApiKey(ctx):
case initContextWithBasicAuth(ctx, orgId): case initContextWithBasicAuth(ctx, orgId):
case initContextWithAuthProxy(ctx, orgId): case initContextWithAuthProxy(remoteCache, ctx, orgId):
case initContextWithToken(ats, ctx, orgId): case initContextWithToken(ats, ctx, orgId):
case initContextWithAnonymousUser(ctx): case initContextWithAnonymousUser(ctx):
} }

View File

@ -2,6 +2,7 @@ package middleware
import ( import (
"encoding/json" "encoding/json"
"fmt"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"path/filepath" "path/filepath"
@ -10,6 +11,7 @@ import (
msession "github.com/go-macaron/session" msession "github.com/go-macaron/session"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/remotecache"
m "github.com/grafana/grafana/pkg/models" m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/auth" "github.com/grafana/grafana/pkg/services/auth"
"github.com/grafana/grafana/pkg/services/session" "github.com/grafana/grafana/pkg/services/session"
@ -23,29 +25,29 @@ func TestMiddlewareContext(t *testing.T) {
setting.ERR_TEMPLATE_NAME = "error-template" setting.ERR_TEMPLATE_NAME = "error-template"
Convey("Given the grafana middleware", t, func() { Convey("Given the grafana middleware", t, func() {
middlewareScenario("middleware should add context to injector", func(sc *scenarioContext) { middlewareScenario(t, "middleware should add context to injector", func(sc *scenarioContext) {
sc.fakeReq("GET", "/").exec() sc.fakeReq("GET", "/").exec()
So(sc.context, ShouldNotBeNil) So(sc.context, ShouldNotBeNil)
}) })
middlewareScenario("Default middleware should allow get request", func(sc *scenarioContext) { middlewareScenario(t, "Default middleware should allow get request", func(sc *scenarioContext) {
sc.fakeReq("GET", "/").exec() sc.fakeReq("GET", "/").exec()
So(sc.resp.Code, ShouldEqual, 200) So(sc.resp.Code, ShouldEqual, 200)
}) })
middlewareScenario("middleware should add Cache-Control header for GET requests to API", func(sc *scenarioContext) { middlewareScenario(t, "middleware should add Cache-Control header for GET requests to API", func(sc *scenarioContext) {
sc.fakeReq("GET", "/api/search").exec() sc.fakeReq("GET", "/api/search").exec()
So(sc.resp.Header().Get("Cache-Control"), ShouldEqual, "no-cache") So(sc.resp.Header().Get("Cache-Control"), ShouldEqual, "no-cache")
So(sc.resp.Header().Get("Pragma"), ShouldEqual, "no-cache") So(sc.resp.Header().Get("Pragma"), ShouldEqual, "no-cache")
So(sc.resp.Header().Get("Expires"), ShouldEqual, "-1") So(sc.resp.Header().Get("Expires"), ShouldEqual, "-1")
}) })
middlewareScenario("middleware should not add Cache-Control header to for non-API GET requests", func(sc *scenarioContext) { middlewareScenario(t, "middleware should not add Cache-Control header to for non-API GET requests", func(sc *scenarioContext) {
sc.fakeReq("GET", "/").exec() sc.fakeReq("GET", "/").exec()
So(sc.resp.Header().Get("Cache-Control"), ShouldBeEmpty) So(sc.resp.Header().Get("Cache-Control"), ShouldBeEmpty)
}) })
middlewareScenario("Invalid api key", func(sc *scenarioContext) { middlewareScenario(t, "Invalid api key", func(sc *scenarioContext) {
sc.apiKey = "invalid_key_test" sc.apiKey = "invalid_key_test"
sc.fakeReq("GET", "/").exec() sc.fakeReq("GET", "/").exec()
@ -59,7 +61,7 @@ func TestMiddlewareContext(t *testing.T) {
}) })
}) })
middlewareScenario("Using basic auth", func(sc *scenarioContext) { middlewareScenario(t, "Using basic auth", func(sc *scenarioContext) {
bus.AddHandler("test", func(query *m.GetUserByLoginQuery) error { bus.AddHandler("test", func(query *m.GetUserByLoginQuery) error {
query.Result = &m.User{ query.Result = &m.User{
@ -89,7 +91,7 @@ func TestMiddlewareContext(t *testing.T) {
}) })
}) })
middlewareScenario("Valid api key", func(sc *scenarioContext) { middlewareScenario(t, "Valid api key", func(sc *scenarioContext) {
keyhash := util.EncodePassword("v5nAwpMafFP6znaS4urhdWDLS5511M42", "asd") keyhash := util.EncodePassword("v5nAwpMafFP6znaS4urhdWDLS5511M42", "asd")
bus.AddHandler("test", func(query *m.GetApiKeyByNameQuery) error { bus.AddHandler("test", func(query *m.GetApiKeyByNameQuery) error {
@ -110,7 +112,7 @@ func TestMiddlewareContext(t *testing.T) {
}) })
}) })
middlewareScenario("Valid api key, but does not match db hash", func(sc *scenarioContext) { middlewareScenario(t, "Valid api key, but does not match db hash", func(sc *scenarioContext) {
keyhash := "something_not_matching" keyhash := "something_not_matching"
bus.AddHandler("test", func(query *m.GetApiKeyByNameQuery) error { bus.AddHandler("test", func(query *m.GetApiKeyByNameQuery) error {
@ -126,7 +128,7 @@ func TestMiddlewareContext(t *testing.T) {
}) })
}) })
middlewareScenario("Valid api key via Basic auth", func(sc *scenarioContext) { middlewareScenario(t, "Valid api key via Basic auth", func(sc *scenarioContext) {
keyhash := util.EncodePassword("v5nAwpMafFP6znaS4urhdWDLS5511M42", "asd") keyhash := util.EncodePassword("v5nAwpMafFP6znaS4urhdWDLS5511M42", "asd")
bus.AddHandler("test", func(query *m.GetApiKeyByNameQuery) error { bus.AddHandler("test", func(query *m.GetApiKeyByNameQuery) error {
@ -148,7 +150,7 @@ func TestMiddlewareContext(t *testing.T) {
}) })
}) })
middlewareScenario("Non-expired auth token in cookie which not are being rotated", func(sc *scenarioContext) { middlewareScenario(t, "Non-expired auth token in cookie which not are being rotated", func(sc *scenarioContext) {
sc.withTokenSessionCookie("token") sc.withTokenSessionCookie("token")
bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error { bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error {
@ -177,7 +179,7 @@ func TestMiddlewareContext(t *testing.T) {
}) })
}) })
middlewareScenario("Non-expired auth token in cookie which are being rotated", func(sc *scenarioContext) { middlewareScenario(t, "Non-expired auth token in cookie which are being rotated", func(sc *scenarioContext) {
sc.withTokenSessionCookie("token") sc.withTokenSessionCookie("token")
bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error { bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error {
@ -224,7 +226,7 @@ func TestMiddlewareContext(t *testing.T) {
}) })
}) })
middlewareScenario("Invalid/expired auth token in cookie", func(sc *scenarioContext) { middlewareScenario(t, "Invalid/expired auth token in cookie", func(sc *scenarioContext) {
sc.withTokenSessionCookie("token") sc.withTokenSessionCookie("token")
sc.userAuthTokenService.LookupTokenProvider = func(unhashedToken string) (*m.UserToken, error) { sc.userAuthTokenService.LookupTokenProvider = func(unhashedToken string) (*m.UserToken, error) {
@ -240,7 +242,7 @@ func TestMiddlewareContext(t *testing.T) {
}) })
}) })
middlewareScenario("When anonymous access is enabled", func(sc *scenarioContext) { middlewareScenario(t, "When anonymous access is enabled", func(sc *scenarioContext) {
setting.AnonymousEnabled = true setting.AnonymousEnabled = true
setting.AnonymousOrgName = "test" setting.AnonymousOrgName = "test"
setting.AnonymousOrgRole = string(m.ROLE_EDITOR) setting.AnonymousOrgRole = string(m.ROLE_EDITOR)
@ -265,287 +267,192 @@ func TestMiddlewareContext(t *testing.T) {
}) })
}) })
middlewareScenario("When auth_proxy is enabled enabled and user exists", func(sc *scenarioContext) { Convey("auth_proxy", func() {
setting.AuthProxyEnabled = true setting.AuthProxyEnabled = true
setting.AuthProxyHeaderName = "X-WEBAUTH-USER" setting.AuthProxyWhitelist = ""
setting.AuthProxyHeaderProperty = "username"
setting.LdapEnabled = false
bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error {
query.Result = &m.SignedInUser{OrgId: 2, UserId: 12}
return nil
})
bus.AddHandler("test", func(cmd *m.UpsertUserCommand) error {
cmd.Result = &m.User{Id: 12}
return nil
})
setting.SessionOptions = msession.Options{}
sc.fakeReq("GET", "/")
sc.req.Header.Add("X-WEBAUTH-USER", "torkelo")
sc.exec()
Convey("should init context with user info", func() {
So(sc.context.IsSignedIn, ShouldBeTrue)
So(sc.context.UserId, ShouldEqual, 12)
So(sc.context.OrgId, ShouldEqual, 2)
})
})
middlewareScenario("When auth_proxy is enabled enabled and user does not exists", func(sc *scenarioContext) {
setting.AuthProxyEnabled = true
setting.AuthProxyHeaderName = "X-WEBAUTH-USER"
setting.AuthProxyHeaderProperty = "username"
setting.AuthProxyAutoSignUp = true setting.AuthProxyAutoSignUp = true
setting.LdapEnabled = false setting.LdapEnabled = true
setting.AuthProxyHeaderName = "X-WEBAUTH-USER"
setting.AuthProxyHeaderProperty = "username"
name := "markelog"
bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error { middlewareScenario(t, "should sync the user if it's not in the cache", func(sc *scenarioContext) {
if query.UserId > 0 { called := false
query.Result = &m.SignedInUser{OrgId: 4, UserId: 33} syncGrafanaUserWithLdapUser = func(query *m.LoginUserQuery) error {
called = true
query.User = &m.User{Id: 32}
return nil return nil
} }
return m.ErrUserNotFound
bus.AddHandler("test", func(query *m.UpsertUserCommand) error {
query.Result = &m.User{Id: 32}
return nil
})
bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error {
query.Result = &m.SignedInUser{OrgId: 4, UserId: 32}
return nil
})
sc.fakeReq("GET", "/")
sc.req.Header.Add(setting.AuthProxyHeaderName, name)
sc.exec()
Convey("Should init user via ldap", func() {
So(called, ShouldBeTrue)
So(sc.context.IsSignedIn, ShouldBeTrue)
So(sc.context.UserId, ShouldEqual, 32)
So(sc.context.OrgId, ShouldEqual, 4)
})
}) })
bus.AddHandler("test", func(cmd *m.UpsertUserCommand) error { middlewareScenario(t, "should not sync the user if it's in the cache", func(sc *scenarioContext) {
cmd.Result = &m.User{Id: 33} called := false
return nil syncGrafanaUserWithLdapUser = func(query *m.LoginUserQuery) error {
called = true
query.User = &m.User{Id: 32}
return nil
}
bus.AddHandler("test", func(query *m.UpsertUserCommand) error {
query.Result = &m.User{Id: 32}
return nil
})
bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error {
query.Result = &m.SignedInUser{OrgId: 4, UserId: 32}
return nil
})
key := fmt.Sprintf(cachePrefix, name)
sc.remoteCacheService.Set(key, int64(33), 0)
sc.fakeReq("GET", "/")
sc.req.Header.Add(setting.AuthProxyHeaderName, name)
sc.exec()
cacheValue, cacheErr := sc.remoteCacheService.Get(key)
Convey("Should init user via cache", func() {
So(called, ShouldBeFalse)
So(sc.context.IsSignedIn, ShouldBeTrue)
So(sc.context.UserId, ShouldEqual, 32)
So(sc.context.OrgId, ShouldEqual, 4)
So(cacheValue, ShouldEqual, 33)
So(cacheErr, ShouldBeNil)
})
}) })
sc.fakeReq("GET", "/") middlewareScenario(t, "should create an user from a header", func(sc *scenarioContext) {
sc.req.Header.Add("X-WEBAUTH-USER", "torkelo") setting.LdapEnabled = false
sc.exec() setting.AuthProxyAutoSignUp = true
Convey("Should create user if auto sign up is enabled", func() { bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error {
So(sc.context.IsSignedIn, ShouldBeTrue) if query.UserId > 0 {
So(sc.context.UserId, ShouldEqual, 33) query.Result = &m.SignedInUser{OrgId: 4, UserId: 33}
So(sc.context.OrgId, ShouldEqual, 4) return nil
}
return m.ErrUserNotFound
})
}) bus.AddHandler("test", func(cmd *m.UpsertUserCommand) error {
}) cmd.Result = &m.User{Id: 33}
return nil
})
middlewareScenario("When auth_proxy is enabled and IPv4 request RemoteAddr is not trusted", func(sc *scenarioContext) { sc.fakeReq("GET", "/")
setting.AuthProxyEnabled = true sc.req.Header.Add(setting.AuthProxyHeaderName, name)
setting.AuthProxyHeaderName = "X-WEBAUTH-USER" sc.exec()
setting.AuthProxyHeaderProperty = "username"
setting.AuthProxyWhitelist = "192.168.1.1, 2001::23"
sc.fakeReq("GET", "/") Convey("Should create user from header info", func() {
sc.req.Header.Add("X-WEBAUTH-USER", "torkelo") So(sc.context.IsSignedIn, ShouldBeTrue)
sc.req.RemoteAddr = "192.168.3.1:12345" So(sc.context.UserId, ShouldEqual, 33)
sc.exec() So(sc.context.OrgId, ShouldEqual, 4)
})
Convey("should return 407 status code", func() {
So(sc.resp.Code, ShouldEqual, 407)
So(sc.resp.Body.String(), ShouldContainSubstring, "Request for user (torkelo) from 192.168.3.1 is not from the authentication proxy")
})
})
middlewareScenario("When auth_proxy is enabled and IPv4 request RemoteAddr is not within trusted CIDR block", func(sc *scenarioContext) {
setting.AuthProxyEnabled = true
setting.AuthProxyHeaderName = "X-WEBAUTH-USER"
setting.AuthProxyHeaderProperty = "username"
setting.AuthProxyWhitelist = "192.168.1.0/24, 2001::0/120"
sc.fakeReq("GET", "/")
sc.req.Header.Add("X-WEBAUTH-USER", "torkelo")
sc.req.RemoteAddr = "192.168.3.1:12345"
sc.exec()
Convey("should return 407 status code", func() {
So(sc.resp.Code, ShouldEqual, 407)
So(sc.resp.Body.String(), ShouldContainSubstring, "Request for user (torkelo) from 192.168.3.1 is not from the authentication proxy")
})
})
middlewareScenario("When auth_proxy is enabled and IPv6 request RemoteAddr is not trusted", func(sc *scenarioContext) {
setting.AuthProxyEnabled = true
setting.AuthProxyHeaderName = "X-WEBAUTH-USER"
setting.AuthProxyHeaderProperty = "username"
setting.AuthProxyWhitelist = "192.168.1.1, 2001::23"
sc.fakeReq("GET", "/")
sc.req.Header.Add("X-WEBAUTH-USER", "torkelo")
sc.req.RemoteAddr = "[2001:23]:12345"
sc.exec()
Convey("should return 407 status code", func() {
So(sc.resp.Code, ShouldEqual, 407)
So(sc.resp.Body.String(), ShouldContainSubstring, "Request for user (torkelo) from 2001:23 is not from the authentication proxy")
})
})
middlewareScenario("When auth_proxy is enabled and IPv6 request RemoteAddr is not within trusted CIDR block", func(sc *scenarioContext) {
setting.AuthProxyEnabled = true
setting.AuthProxyHeaderName = "X-WEBAUTH-USER"
setting.AuthProxyHeaderProperty = "username"
setting.AuthProxyWhitelist = "192.168.1.0/24, 2001::0/120"
sc.fakeReq("GET", "/")
sc.req.Header.Add("X-WEBAUTH-USER", "torkelo")
sc.req.RemoteAddr = "[2001:23]:12345"
sc.exec()
Convey("should return 407 status code", func() {
So(sc.resp.Code, ShouldEqual, 407)
So(sc.resp.Body.String(), ShouldContainSubstring, "Request for user (torkelo) from 2001:23 is not from the authentication proxy")
})
})
middlewareScenario("When auth_proxy is enabled and request RemoteAddr is trusted", func(sc *scenarioContext) {
setting.AuthProxyEnabled = true
setting.AuthProxyHeaderName = "X-WEBAUTH-USER"
setting.AuthProxyHeaderProperty = "username"
setting.AuthProxyWhitelist = "192.168.1.1, 2001::23"
bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error {
query.Result = &m.SignedInUser{OrgId: 4, UserId: 33}
return nil
}) })
bus.AddHandler("test", func(cmd *m.UpsertUserCommand) error { middlewareScenario(t, "should get an existing user from header", func(sc *scenarioContext) {
cmd.Result = &m.User{Id: 33} setting.LdapEnabled = false
return nil
bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error {
query.Result = &m.SignedInUser{OrgId: 2, UserId: 12}
return nil
})
bus.AddHandler("test", func(cmd *m.UpsertUserCommand) error {
cmd.Result = &m.User{Id: 12}
return nil
})
sc.fakeReq("GET", "/")
sc.req.Header.Add(setting.AuthProxyHeaderName, name)
sc.exec()
Convey("should init context with user info", func() {
So(sc.context.IsSignedIn, ShouldBeTrue)
So(sc.context.UserId, ShouldEqual, 12)
So(sc.context.OrgId, ShouldEqual, 2)
})
}) })
sc.fakeReq("GET", "/") middlewareScenario(t, "should allow the request from whitelist IP", func(sc *scenarioContext) {
sc.req.Header.Add("X-WEBAUTH-USER", "torkelo") setting.AuthProxyWhitelist = "192.168.1.0/24, 2001::0/120"
sc.req.RemoteAddr = "[2001::23]:12345" setting.LdapEnabled = false
sc.exec()
Convey("Should init context with user info", func() { bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error {
So(sc.context.IsSignedIn, ShouldBeTrue) query.Result = &m.SignedInUser{OrgId: 4, UserId: 33}
So(sc.context.UserId, ShouldEqual, 33) return nil
So(sc.context.OrgId, ShouldEqual, 4) })
})
})
middlewareScenario("When auth_proxy is enabled and IPv4 request RemoteAddr is within trusted CIDR block", func(sc *scenarioContext) { bus.AddHandler("test", func(cmd *m.UpsertUserCommand) error {
setting.AuthProxyEnabled = true cmd.Result = &m.User{Id: 33}
setting.AuthProxyHeaderName = "X-WEBAUTH-USER" return nil
setting.AuthProxyHeaderProperty = "username" })
setting.AuthProxyWhitelist = "192.168.1.0/24, 2001::0/120"
bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error { sc.fakeReq("GET", "/")
query.Result = &m.SignedInUser{OrgId: 4, UserId: 33} sc.req.Header.Add(setting.AuthProxyHeaderName, name)
return nil sc.req.RemoteAddr = "[2001::23]:12345"
sc.exec()
Convey("Should init context with user info", func() {
So(sc.context.IsSignedIn, ShouldBeTrue)
So(sc.context.UserId, ShouldEqual, 33)
So(sc.context.OrgId, ShouldEqual, 4)
})
}) })
bus.AddHandler("test", func(cmd *m.UpsertUserCommand) error { middlewareScenario(t, "should not allow the request from whitelist IP", func(sc *scenarioContext) {
cmd.Result = &m.User{Id: 33} setting.AuthProxyWhitelist = "8.8.8.8"
return nil setting.LdapEnabled = false
})
sc.fakeReq("GET", "/") bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error {
sc.req.Header.Add("X-WEBAUTH-USER", "torkelo") query.Result = &m.SignedInUser{OrgId: 4, UserId: 33}
sc.req.RemoteAddr = "192.168.1.10:12345" return nil
sc.exec() })
Convey("Should init context with user info", func() { bus.AddHandler("test", func(cmd *m.UpsertUserCommand) error {
So(sc.context.IsSignedIn, ShouldBeTrue) cmd.Result = &m.User{Id: 33}
So(sc.context.UserId, ShouldEqual, 33) return nil
So(sc.context.OrgId, ShouldEqual, 4) })
})
})
middlewareScenario("When auth_proxy is enabled and IPv6 request RemoteAddr is within trusted CIDR block", func(sc *scenarioContext) { sc.fakeReq("GET", "/")
setting.AuthProxyEnabled = true sc.req.Header.Add(setting.AuthProxyHeaderName, name)
setting.AuthProxyHeaderName = "X-WEBAUTH-USER" sc.req.RemoteAddr = "[2001::23]:12345"
setting.AuthProxyHeaderProperty = "username" sc.exec()
setting.AuthProxyWhitelist = "192.168.1.0/24, 2001::0/120"
bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error { Convey("should return 407 status code", func() {
query.Result = &m.SignedInUser{OrgId: 4, UserId: 33} So(sc.resp.Code, ShouldEqual, 407)
return nil So(sc.context, ShouldBeNil)
}) })
bus.AddHandler("test", func(cmd *m.UpsertUserCommand) error {
cmd.Result = &m.User{Id: 33}
return nil
})
sc.fakeReq("GET", "/")
sc.req.Header.Add("X-WEBAUTH-USER", "torkelo")
sc.req.RemoteAddr = "[2001::23]:12345"
sc.exec()
Convey("Should init context with user info", func() {
So(sc.context.IsSignedIn, ShouldBeTrue)
So(sc.context.UserId, ShouldEqual, 33)
So(sc.context.OrgId, ShouldEqual, 4)
})
})
middlewareScenario("When session exists for previous user, create a new session", func(sc *scenarioContext) {
setting.AuthProxyEnabled = true
setting.AuthProxyHeaderName = "X-WEBAUTH-USER"
setting.AuthProxyHeaderProperty = "username"
setting.AuthProxyWhitelist = ""
bus.AddHandler("test", func(query *m.UpsertUserCommand) error {
query.Result = &m.User{Id: 32}
return nil
})
bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error {
query.Result = &m.SignedInUser{OrgId: 4, UserId: 32}
return nil
})
// create session
sc.fakeReq("GET", "/").handler(func(c *m.ReqContext) {
c.Session.Set(session.SESS_KEY_USERID, int64(33))
}).exec()
oldSessionID := sc.context.Session.ID()
sc.req.Header.Add("X-WEBAUTH-USER", "torkelo")
sc.exec()
newSessionID := sc.context.Session.ID()
Convey("Should not share session with other user", func() {
So(oldSessionID, ShouldNotEqual, newSessionID)
})
})
middlewareScenario("When auth_proxy and ldap enabled call sync with ldap user", func(sc *scenarioContext) {
setting.AuthProxyEnabled = true
setting.AuthProxyHeaderName = "X-WEBAUTH-USER"
setting.AuthProxyHeaderProperty = "username"
setting.AuthProxyWhitelist = ""
setting.LdapEnabled = true
called := false
syncGrafanaUserWithLdapUser = func(query *m.LoginUserQuery) error {
called = true
query.User = &m.User{Id: 32}
return nil
}
bus.AddHandler("test", func(query *m.UpsertUserCommand) error {
query.Result = &m.User{Id: 32}
return nil
})
bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error {
query.Result = &m.SignedInUser{OrgId: 4, UserId: 32}
return nil
})
sc.fakeReq("GET", "/")
sc.req.Header.Add("X-WEBAUTH-USER", "torkelo")
sc.exec()
Convey("Should call syncGrafanaUserWithLdapUser", func() {
So(called, ShouldBeTrue)
}) })
}) })
}) })
} }
func middlewareScenario(desc string, fn scenarioFunc) { func middlewareScenario(t *testing.T, desc string, fn scenarioFunc) {
Convey(desc, func() { Convey(desc, func() {
defer bus.ClearBusHandlers() defer bus.ClearBusHandlers()
@ -562,9 +469,10 @@ func middlewareScenario(desc string, fn scenarioFunc) {
Delims: macaron.Delims{Left: "[[", Right: "]]"}, Delims: macaron.Delims{Left: "[[", Right: "]]"},
})) }))
session.Init(&msession.Options{}, 0)
sc.userAuthTokenService = auth.NewFakeUserAuthTokenService() sc.userAuthTokenService = auth.NewFakeUserAuthTokenService()
sc.m.Use(GetContextHandler(sc.userAuthTokenService)) sc.remoteCacheService = remotecache.NewFakeStore(t)
sc.m.Use(GetContextHandler(sc.userAuthTokenService, sc.remoteCacheService))
// mock out gc goroutine // mock out gc goroutine
session.StartSessionGC = func() {} session.StartSessionGC = func() {}
setting.SessionOptions = msession.Options{} setting.SessionOptions = msession.Options{}
@ -597,6 +505,7 @@ type scenarioContext struct {
defaultHandler macaron.Handler defaultHandler macaron.Handler
url string url string
userAuthTokenService *auth.FakeUserAuthTokenService userAuthTokenService *auth.FakeUserAuthTokenService
remoteCacheService *remotecache.RemoteCache
req *http.Request req *http.Request
} }
@ -622,13 +531,6 @@ func (sc *scenarioContext) fakeReq(method, url string) *scenarioContext {
So(err, ShouldBeNil) So(err, ShouldBeNil)
sc.req = req sc.req = req
// add session cookie from last request
if sc.context != nil {
if sc.context.Session.ID() != "" {
req.Header.Add("Cookie", "grafana_sess="+sc.context.Session.ID()+";")
}
}
return sc return sc
} }

View File

@ -1,9 +1,8 @@
package middleware package middleware
import ( import (
"testing"
"fmt" "fmt"
"testing"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
m "github.com/grafana/grafana/pkg/models" m "github.com/grafana/grafana/pkg/models"
@ -13,7 +12,7 @@ import (
func TestOrgRedirectMiddleware(t *testing.T) { func TestOrgRedirectMiddleware(t *testing.T) {
Convey("Can redirect to correct org", t, func() { Convey("Can redirect to correct org", t, func() {
middlewareScenario("when setting a correct org for the user", func(sc *scenarioContext) { middlewareScenario(t, "when setting a correct org for the user", func(sc *scenarioContext) {
sc.withTokenSessionCookie("token") sc.withTokenSessionCookie("token")
bus.AddHandler("test", func(query *m.SetUsingOrgCommand) error { bus.AddHandler("test", func(query *m.SetUsingOrgCommand) error {
return nil return nil
@ -39,7 +38,7 @@ func TestOrgRedirectMiddleware(t *testing.T) {
}) })
}) })
middlewareScenario("when setting an invalid org for user", func(sc *scenarioContext) { middlewareScenario(t, "when setting an invalid org for user", func(sc *scenarioContext) {
sc.withTokenSessionCookie("token") sc.withTokenSessionCookie("token")
bus.AddHandler("test", func(query *m.SetUsingOrgCommand) error { bus.AddHandler("test", func(query *m.SetUsingOrgCommand) error {
return fmt.Errorf("") return fmt.Errorf("")

View File

@ -3,11 +3,10 @@ package middleware
import ( import (
"testing" "testing"
"github.com/grafana/grafana/pkg/services/auth"
"github.com/grafana/grafana/pkg/services/quota"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
m "github.com/grafana/grafana/pkg/models" m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/auth"
"github.com/grafana/grafana/pkg/services/quota"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
. "github.com/smartystreets/goconvey/convey" . "github.com/smartystreets/goconvey/convey"
) )
@ -43,7 +42,7 @@ func TestMiddlewareQuota(t *testing.T) {
} }
QuotaFn := Quota(qs) QuotaFn := Quota(qs)
middlewareScenario("with user not logged in", func(sc *scenarioContext) { middlewareScenario(t, "with user not logged in", func(sc *scenarioContext) {
bus.AddHandler("globalQuota", func(query *m.GetGlobalQuotaByTargetQuery) error { bus.AddHandler("globalQuota", func(query *m.GetGlobalQuotaByTargetQuery) error {
query.Result = &m.GlobalQuotaDTO{ query.Result = &m.GlobalQuotaDTO{
Target: query.Target, Target: query.Target,
@ -81,7 +80,7 @@ func TestMiddlewareQuota(t *testing.T) {
}) })
}) })
middlewareScenario("with user logged in", func(sc *scenarioContext) { middlewareScenario(t, "with user logged in", func(sc *scenarioContext) {
sc.withTokenSessionCookie("token") sc.withTokenSessionCookie("token")
bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error { bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error {
query.Result = &m.SignedInUser{OrgId: 2, UserId: 12} query.Result = &m.SignedInUser{OrgId: 2, UserId: 12}

View File

@ -5,6 +5,7 @@ import (
"testing" "testing"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/remotecache"
m "github.com/grafana/grafana/pkg/models" m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/auth" "github.com/grafana/grafana/pkg/services/auth"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
@ -17,7 +18,7 @@ func TestRecoveryMiddleware(t *testing.T) {
Convey("Given an api route that panics", t, func() { Convey("Given an api route that panics", t, func() {
apiURL := "/api/whatever" apiURL := "/api/whatever"
recoveryScenario("recovery middleware should return json", apiURL, func(sc *scenarioContext) { recoveryScenario(t, "recovery middleware should return json", apiURL, func(sc *scenarioContext) {
sc.handlerFunc = PanicHandler sc.handlerFunc = PanicHandler
sc.fakeReq("GET", apiURL).exec() sc.fakeReq("GET", apiURL).exec()
sc.req.Header.Add("content-type", "application/json") sc.req.Header.Add("content-type", "application/json")
@ -30,7 +31,7 @@ func TestRecoveryMiddleware(t *testing.T) {
Convey("Given a non-api route that panics", t, func() { Convey("Given a non-api route that panics", t, func() {
apiURL := "/whatever" apiURL := "/whatever"
recoveryScenario("recovery middleware should return html", apiURL, func(sc *scenarioContext) { recoveryScenario(t, "recovery middleware should return html", apiURL, func(sc *scenarioContext) {
sc.handlerFunc = PanicHandler sc.handlerFunc = PanicHandler
sc.fakeReq("GET", apiURL).exec() sc.fakeReq("GET", apiURL).exec()
@ -45,7 +46,7 @@ func PanicHandler(c *m.ReqContext) {
panic("Handler has panicked") panic("Handler has panicked")
} }
func recoveryScenario(desc string, url string, fn scenarioFunc) { func recoveryScenario(t *testing.T, desc string, url string, fn scenarioFunc) {
Convey(desc, func() { Convey(desc, func() {
defer bus.ClearBusHandlers() defer bus.ClearBusHandlers()
@ -64,7 +65,9 @@ func recoveryScenario(desc string, url string, fn scenarioFunc) {
})) }))
sc.userAuthTokenService = auth.NewFakeUserAuthTokenService() sc.userAuthTokenService = auth.NewFakeUserAuthTokenService()
sc.m.Use(GetContextHandler(sc.userAuthTokenService)) sc.remoteCacheService = remotecache.NewFakeStore(t)
sc.m.Use(GetContextHandler(sc.userAuthTokenService, sc.remoteCacheService))
// mock out gc goroutine // mock out gc goroutine
sc.m.Use(OrgRedirect()) sc.m.Use(OrgRedirect())
sc.m.Use(AddDefaultResponseHeaders()) sc.m.Use(AddDefaultResponseHeaders())

View File

@ -11,6 +11,8 @@ import (
"testing" "testing"
"time" "time"
"github.com/go-sql-driver/mysql"
"github.com/go-xorm/xorm"
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/log"
m "github.com/grafana/grafana/pkg/models" m "github.com/grafana/grafana/pkg/models"
@ -21,12 +23,8 @@ import (
"github.com/grafana/grafana/pkg/services/sqlstore/migrator" "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
"github.com/grafana/grafana/pkg/services/sqlstore/sqlutil" "github.com/grafana/grafana/pkg/services/sqlstore/sqlutil"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
"github.com/go-sql-driver/mysql"
"github.com/go-xorm/xorm"
_ "github.com/grafana/grafana/pkg/tsdb/mssql" _ "github.com/grafana/grafana/pkg/tsdb/mssql"
"github.com/grafana/grafana/pkg/util"
_ "github.com/lib/pq" _ "github.com/lib/pq"
sqlite3 "github.com/mattn/go-sqlite3" sqlite3 "github.com/mattn/go-sqlite3"
) )