diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e8348528c4..bcaabcb68a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - [Issue #1660](https://github.com/grafana/grafana/issues/1660). OAuth: Specify allowed email address domains for google or and github oauth logins **Fixes** +- [Issue #1649](https://github.com/grafana/grafana/issues/1649). HTTP API: grafana /render calls nows with api keys - [Issue #1667](https://github.com/grafana/grafana/issues/1667). Datasource proxy & session timeout fix (casued 401 Unauthorized error after a while) - [Issue #1707](https://github.com/grafana/grafana/issues/1707). Unsaved changes: Do not show for snapshots, scripted and file based dashboards - [Issue #1703](https://github.com/grafana/grafana/issues/1703). Unsaved changes: Do not show for users with role `Viewer` diff --git a/pkg/api/render.go b/pkg/api/render.go index 0398d455cc8..728128acaab 100644 --- a/pkg/api/render.go +++ b/pkg/api/render.go @@ -13,6 +13,18 @@ import ( func RenderToPng(c *middleware.Context) { queryReader := util.NewUrlQueryReader(c.Req.URL) queryParams := fmt.Sprintf("?%s", c.Req.URL.RawQuery) + sessionId := c.Session.ID() + + // Handle api calls authenticated without session + if sessionId == "" && c.ApiKeyId != 0 { + c.Session.Start(c) + c.Session.Set(middleware.SESS_KEY_APIKEY, c.ApiKeyId) + // release will make sure the new session is persisted before + // we spin up phantomjs + c.Session.Release() + // cleanup session after render is complete + defer func() { c.Session.Destory(c) }() + } renderOpts := &renderer.RenderOpts{ Url: c.Params("*") + queryParams, diff --git a/pkg/components/renderer/renderer.go b/pkg/components/renderer/renderer.go index 054632395ce..619a302210e 100644 --- a/pkg/components/renderer/renderer.go +++ b/pkg/components/renderer/renderer.go @@ -1,8 +1,6 @@ package renderer import ( - "crypto/md5" - "encoding/hex" "io" "os" "os/exec" @@ -11,6 +9,7 @@ import ( "github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/util" ) type RenderOpts struct { @@ -24,7 +23,7 @@ func RenderToPng(params *RenderOpts) (string, error) { log.Info("PhantomRenderer::renderToPng url %v", params.Url) binPath, _ := filepath.Abs(filepath.Join(setting.PhantomDir, "phantomjs")) scriptPath, _ := filepath.Abs(filepath.Join(setting.PhantomDir, "render.js")) - pngPath, _ := filepath.Abs(filepath.Join(setting.ImagesDir, getHash(params.Url))) + pngPath, _ := filepath.Abs(filepath.Join(setting.ImagesDir, util.GetRandomString(20))) pngPath = pngPath + ".png" cmd := exec.Command(binPath, scriptPath, "url="+params.Url, "width="+params.Width, @@ -64,9 +63,3 @@ func RenderToPng(params *RenderOpts) (string, error) { return pngPath, nil } - -func getHash(text string) string { - hasher := md5.New() - hasher.Write([]byte(text)) - return hex.EncodeToString(hasher.Sum(nil)) -} diff --git a/pkg/middleware/middleware.go b/pkg/middleware/middleware.go index 0c2f17b0d60..b93cd517364 100644 --- a/pkg/middleware/middleware.go +++ b/pkg/middleware/middleware.go @@ -34,8 +34,14 @@ func GetContextHandler() macaron.Handler { AllowAnonymous: false, } + // the order in which these are tested are important + // look for api key in Authorization header first + // then init session and look for userId in session + // then look for api key in session (special case for render calls via api) + // then test if anonymous access is enabled if initContextWithApiKey(ctx) || initContextWithUserSessionCookie(ctx) || + initContextWithApiKeyFromSession(ctx) || initContextWithAnonymousUser(ctx) { } @@ -77,7 +83,6 @@ func initContextWithUserSessionCookie(ctx *Context) bool { 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 @@ -114,8 +119,29 @@ func initContextWithApiKey(ctx *Context) bool { ctx.IsSignedIn = true ctx.SignedInUser = &m.SignedInUser{} + ctx.OrgRole = apikey.Role + ctx.ApiKeyId = apikey.Id + ctx.OrgId = apikey.OrgId + return true + } +} + +// special case for panel render calls with api key +func initContextWithApiKeyFromSession(ctx *Context) bool { + keyId := ctx.Session.Get(SESS_KEY_APIKEY) + if keyId == nil { + return false + } + + keyQuery := m.GetApiKeyByIdQuery{ApiKeyId: keyId.(int64)} + if err := bus.Dispatch(&keyQuery); err != nil { + log.Error(3, "Failed to get api key by id", err) + return false + } else { + apikey := keyQuery.Result - // TODO: fix this + ctx.IsSignedIn = true + ctx.SignedInUser = &m.SignedInUser{} ctx.OrgRole = apikey.Role ctx.ApiKeyId = apikey.Id ctx.OrgId = apikey.OrgId diff --git a/pkg/middleware/session.go b/pkg/middleware/session.go index b0c971276ba..71f87b343ff 100644 --- a/pkg/middleware/session.go +++ b/pkg/middleware/session.go @@ -11,8 +11,8 @@ import ( ) const ( - SESS_KEY_USERID = "uid" - SESS_KEY_FAVORITES = "favorites" + SESS_KEY_USERID = "uid" + SESS_KEY_APIKEY = "apikey_id" // used fror render requests with api keys ) var sessionManager *session.Manager @@ -102,7 +102,10 @@ func (s *SessionWrapper) Release() error { func (s *SessionWrapper) Destory(c *Context) error { if s.session != nil { - return s.manager.Destory(c.Context) + if err := s.manager.Destory(c.Context); err != nil { + return err + } + s.session = nil } return nil } diff --git a/pkg/models/apikey.go b/pkg/models/apikey.go index f550e4433db..a666cb30c61 100644 --- a/pkg/models/apikey.go +++ b/pkg/models/apikey.go @@ -55,6 +55,11 @@ type GetApiKeyByNameQuery struct { Result *ApiKey } +type GetApiKeyByIdQuery struct { + ApiKeyId int64 + Result *ApiKey +} + // ------------------------ // DTO & Projections diff --git a/pkg/services/sqlstore/apikey.go b/pkg/services/sqlstore/apikey.go index f4fc88fa211..1cca7b5e40b 100644 --- a/pkg/services/sqlstore/apikey.go +++ b/pkg/services/sqlstore/apikey.go @@ -10,6 +10,7 @@ import ( func init() { bus.AddHandler("sql", GetApiKeys) + bus.AddHandler("sql", GetApiKeyById) bus.AddHandler("sql", GetApiKeyByName) bus.AddHandler("sql", DeleteApiKey) bus.AddHandler("sql", AddApiKey) @@ -49,6 +50,20 @@ func AddApiKey(cmd *m.AddApiKeyCommand) error { }) } +func GetApiKeyById(query *m.GetApiKeyByIdQuery) error { + var apikey m.ApiKey + has, err := x.Id(query.ApiKeyId).Get(&apikey) + + if err != nil { + return err + } else if has == false { + return m.ErrInvalidApiKey + } + + query.Result = &apikey + return nil +} + func GetApiKeyByName(query *m.GetApiKeyByNameQuery) error { var apikey m.ApiKey has, err := x.Where("org_id=? AND name=?", query.OrgId, query.KeyName).Get(&apikey) diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 0e6684c986f..107a0147385 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -259,6 +259,10 @@ func readSessionConfig() { if SessionOptions.Provider == "file" { os.MkdirAll(path.Dir(SessionOptions.ProviderConfig), os.ModePerm) } + + if SessionOptions.CookiePath == "" { + SessionOptions.CookiePath = "/" + } } var logLevels = map[string]string{