diff --git a/pkg/api/login.go b/pkg/api/login.go index c9b8b598cfe..2e36a83b099 100644 --- a/pkg/api/login.go +++ b/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") } diff --git a/pkg/cmd/web.go b/pkg/cmd/web.go index 1b6e9078f61..5f884ed40b1 100644 --- a/pkg/cmd/web.go +++ b/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 } diff --git a/pkg/middleware/middleware.go b/pkg/middleware/middleware.go index 20e1eb196e5..0c2f17b0d60 100644 --- a/pkg/middleware/middleware.go +++ b/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 { diff --git a/pkg/middleware/session.go b/pkg/middleware/session.go index 1eb1dd5acbf..b0c971276ba 100644 --- a/pkg/middleware/session.go +++ b/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 +} diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 36b76e20de8..0e6684c986f 100644 --- a/pkg/setting/setting.go +++ b/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)