ソースを参照

A big refactoring for how sessions are handled, Api calls that authenticate with api key will no longer create a new session

Torkel Ödegaard 10 年 前
コミット
c07d48d930
5 ファイル変更192 行追加61 行削除
  1. 1 1
      pkg/api/login.go
  2. 2 6
      pkg/cmd/web.go
  3. 86 54
      pkg/middleware/middleware.go
  4. 102 0
      pkg/middleware/session.go
  5. 1 0
      pkg/setting/setting.go

+ 1 - 1
pkg/api/login.go

@@ -139,6 +139,6 @@ func loginUserWithUser(user *m.User, c *middleware.Context) {
 func Logout(c *middleware.Context) {
 	c.SetCookie(setting.CookieUserName, "", -1, setting.AppSubUrl+"/")
 	c.SetCookie(setting.CookieRememberName, "", -1, setting.AppSubUrl+"/")
-	c.Session.Destory(c.Context)
+	c.Session.Destory(c)
 	c.Redirect(setting.AppSubUrl + "/login")
 }

+ 2 - 6
pkg/cmd/web.go

@@ -14,10 +14,6 @@ import (
 
 	"github.com/Unknwon/macaron"
 	"github.com/codegangsta/cli"
-	"github.com/macaron-contrib/session"
-	_ "github.com/macaron-contrib/session/mysql"
-	_ "github.com/macaron-contrib/session/postgres"
-	_ "github.com/macaron-contrib/session/redis"
 
 	"github.com/grafana/grafana/pkg/api"
 	"github.com/grafana/grafana/pkg/api/static"
@@ -54,8 +50,6 @@ func newMacaron() *macaron.Macaron {
 	mapStatic(m, "img", "img")
 	mapStatic(m, "fonts", "fonts")
 
-	m.Use(session.Sessioner(setting.SessionOptions))
-
 	m.Use(macaron.Renderer(macaron.RenderOptions{
 		Directory:  path.Join(setting.StaticRootPath, "views"),
 		IndentJSON: macaron.Env != macaron.PROD,
@@ -63,6 +57,8 @@ func newMacaron() *macaron.Macaron {
 	}))
 
 	m.Use(middleware.GetContextHandler())
+	m.Use(middleware.Sessioner(setting.SessionOptions))
+
 	return m
 }
 

+ 86 - 54
pkg/middleware/middleware.go

@@ -5,7 +5,6 @@ import (
 	"strings"
 
 	"github.com/Unknwon/macaron"
-	"github.com/macaron-contrib/session"
 
 	"github.com/grafana/grafana/pkg/bus"
 	"github.com/grafana/grafana/pkg/components/apikeygen"
@@ -19,78 +18,111 @@ type Context struct {
 	*macaron.Context
 	*m.SignedInUser
 
-	Session session.Store
+	Session SessionStore
 
 	IsSignedIn     bool
 	AllowAnonymous bool
 }
 
 func GetContextHandler() macaron.Handler {
-	return func(c *macaron.Context, sess session.Store) {
+	return func(c *macaron.Context) {
 		ctx := &Context{
 			Context:        c,
-			Session:        sess,
 			SignedInUser:   &m.SignedInUser{},
+			Session:        GetSession(),
 			IsSignedIn:     false,
 			AllowAnonymous: false,
 		}
 
-		// try get account id from request
-		if userId := getRequestUserId(ctx); userId != 0 {
-			query := m.GetSignedInUserQuery{UserId: userId}
-			if err := bus.Dispatch(&query); err != nil {
-				log.Error(3, "Failed to get user by id, %v, %v", userId, err)
-			} else {
-				ctx.SignedInUser = query.Result
-				ctx.IsSignedIn = true
-			}
-		} else if keyString := getApiKey(ctx); keyString != "" {
-			// base64 decode key
-			decoded, err := apikeygen.Decode(keyString)
-			if err != nil {
-				ctx.JsonApiErr(401, "Invalid API key", err)
-				return
-			}
-			// fetch key
-			keyQuery := m.GetApiKeyByNameQuery{KeyName: decoded.Name, OrgId: decoded.OrgId}
-			if err := bus.Dispatch(&keyQuery); err != nil {
-				ctx.JsonApiErr(401, "Invalid API key", err)
-				return
-			} else {
-				apikey := keyQuery.Result
-
-				// validate api key
-				if !apikeygen.IsValid(decoded, apikey.Key) {
-					ctx.JsonApiErr(401, "Invalid API key", err)
-					return
-				}
-
-				ctx.IsSignedIn = true
-				ctx.SignedInUser = &m.SignedInUser{}
-
-				// TODO: fix this
-				ctx.OrgRole = apikey.Role
-				ctx.ApiKeyId = apikey.Id
-				ctx.OrgId = apikey.OrgId
-			}
-		} else if setting.AnonymousEnabled {
-			orgQuery := m.GetOrgByNameQuery{Name: setting.AnonymousOrgName}
-			if err := bus.Dispatch(&orgQuery); err != nil {
-				log.Error(3, "Anonymous access organization error: '%s': %s", setting.AnonymousOrgName, err)
-			} else {
-				ctx.IsSignedIn = false
-				ctx.AllowAnonymous = true
-				ctx.SignedInUser = &m.SignedInUser{}
-				ctx.OrgRole = m.RoleType(setting.AnonymousOrgRole)
-				ctx.OrgId = orgQuery.Result.Id
-				ctx.OrgName = orgQuery.Result.Name
-			}
+		if initContextWithApiKey(ctx) ||
+			initContextWithUserSessionCookie(ctx) ||
+			initContextWithAnonymousUser(ctx) {
 		}
 
 		c.Map(ctx)
 	}
 }
 
+func initContextWithAnonymousUser(ctx *Context) bool {
+	if !setting.AnonymousEnabled {
+		return false
+	}
+
+	orgQuery := m.GetOrgByNameQuery{Name: setting.AnonymousOrgName}
+	if err := bus.Dispatch(&orgQuery); err != nil {
+		log.Error(3, "Anonymous access organization error: '%s': %s", setting.AnonymousOrgName, err)
+		return false
+	} else {
+		ctx.IsSignedIn = false
+		ctx.AllowAnonymous = true
+		ctx.SignedInUser = &m.SignedInUser{}
+		ctx.OrgRole = m.RoleType(setting.AnonymousOrgRole)
+		ctx.OrgId = orgQuery.Result.Id
+		ctx.OrgName = orgQuery.Result.Name
+		return true
+	}
+}
+
+func initContextWithUserSessionCookie(ctx *Context) bool {
+	// initialize session
+	if err := ctx.Session.Start(ctx); err != nil {
+		log.Error(3, "Failed to start session", err)
+		return false
+	}
+
+	var userId int64
+	if userId = getRequestUserId(ctx); userId == 0 {
+		return false
+	}
+
+	query := m.GetSignedInUserQuery{UserId: userId}
+	if err := bus.Dispatch(&query); err != nil {
+		log.Error(3, "Failed to get user by id, %v, %v", userId, err)
+		return false
+	} else {
+		ctx.SignedInUser = query.Result
+		ctx.IsSignedIn = true
+		return true
+	}
+}
+
+func initContextWithApiKey(ctx *Context) bool {
+	var keyString string
+	if keyString = getApiKey(ctx); keyString == "" {
+		return false
+	}
+
+	// base64 decode key
+	decoded, err := apikeygen.Decode(keyString)
+	if err != nil {
+		ctx.JsonApiErr(401, "Invalid API key", err)
+		return true
+	}
+	// fetch key
+	keyQuery := m.GetApiKeyByNameQuery{KeyName: decoded.Name, OrgId: decoded.OrgId}
+	if err := bus.Dispatch(&keyQuery); err != nil {
+		ctx.JsonApiErr(401, "Invalid API key", err)
+		return true
+	} else {
+		apikey := keyQuery.Result
+
+		// validate api key
+		if !apikeygen.IsValid(decoded, apikey.Key) {
+			ctx.JsonApiErr(401, "Invalid API key", err)
+			return true
+		}
+
+		ctx.IsSignedIn = true
+		ctx.SignedInUser = &m.SignedInUser{}
+
+		// TODO: fix this
+		ctx.OrgRole = apikey.Role
+		ctx.ApiKeyId = apikey.Id
+		ctx.OrgId = apikey.OrgId
+		return true
+	}
+}
+
 // Handle handles and logs error by given status.
 func (ctx *Context) Handle(status int, title string, err error) {
 	if err != nil {

+ 102 - 0
pkg/middleware/session.go

@@ -1,6 +1,108 @@
 package middleware
 
+import (
+	"time"
+
+	"github.com/Unknwon/macaron"
+	"github.com/macaron-contrib/session"
+	_ "github.com/macaron-contrib/session/mysql"
+	_ "github.com/macaron-contrib/session/postgres"
+	_ "github.com/macaron-contrib/session/redis"
+)
+
 const (
 	SESS_KEY_USERID    = "uid"
 	SESS_KEY_FAVORITES = "favorites"
 )
+
+var sessionManager *session.Manager
+var sessionOptions session.Options
+
+func startSessionGC() {
+	sessionManager.GC()
+	time.AfterFunc(time.Duration(sessionOptions.Gclifetime)*time.Second, startSessionGC)
+}
+
+func Sessioner(options session.Options) macaron.Handler {
+	var err error
+	sessionOptions = options
+	sessionManager, err = session.NewManager(options.Provider, options)
+	if err != nil {
+		panic(err)
+	}
+
+	go startSessionGC()
+
+	return func(ctx *Context) {
+		ctx.Next()
+
+		if err = ctx.Session.Release(); err != nil {
+			panic("session(release): " + err.Error())
+		}
+	}
+}
+
+func GetSession() SessionStore {
+	return &SessionWrapper{manager: sessionManager}
+}
+
+type SessionStore interface {
+	// Set sets value to given key in session.
+	Set(interface{}, interface{}) error
+	// Get gets value by given key in session.
+	Get(interface{}) interface{}
+	// ID returns current session ID.
+	ID() string
+	// Release releases session resource and save data to provider.
+	Release() error
+	// Destory deletes a session.
+	Destory(*Context) error
+	// init
+	Start(*Context) error
+}
+
+type SessionWrapper struct {
+	session session.RawStore
+	manager *session.Manager
+}
+
+func (s *SessionWrapper) Start(c *Context) error {
+	var err error
+	s.session, err = s.manager.Start(c.Context)
+	return err
+}
+
+func (s *SessionWrapper) Set(k interface{}, v interface{}) error {
+	if s.session != nil {
+		return s.session.Set(k, v)
+	}
+	return nil
+}
+
+func (s *SessionWrapper) Get(k interface{}) interface{} {
+	if s.session != nil {
+		return s.session.Get(k)
+	}
+	return nil
+}
+
+func (s *SessionWrapper) ID() string {
+	if s.session != nil {
+		return s.session.ID()
+	}
+	return ""
+}
+
+func (s *SessionWrapper) Release() error {
+	if s.session != nil {
+		return s.session.Release()
+	}
+	return nil
+}
+
+func (s *SessionWrapper) Destory(c *Context) error {
+	if s.session != nil {
+		return s.manager.Destory(c.Context)
+	}
+	return nil
+}

+ 1 - 0
pkg/setting/setting.go

@@ -254,6 +254,7 @@ func readSessionConfig() {
 	SessionOptions.Secure = sec.Key("cookie_secure").MustBool()
 	SessionOptions.Gclifetime = Cfg.Section("session").Key("gc_interval_time").MustInt64(86400)
 	SessionOptions.Maxlifetime = Cfg.Section("session").Key("session_life_time").MustInt64(86400)
+	SessionOptions.IDLength = 16
 
 	if SessionOptions.Provider == "file" {
 		os.MkdirAll(path.Dir(SessionOptions.ProviderConfig), os.ModePerm)