Browse Source

HTTP API: grafana /render calls nows with api keys, Fixes #1649

Torkel Ödegaard 10 years ago
parent
commit
059db533d5

+ 1 - 0
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
 - [Issue #1660](https://github.com/grafana/grafana/issues/1660). OAuth: Specify allowed email address domains for google or and github oauth logins
 
 
 **Fixes**
 **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 #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 #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`
 - [Issue #1703](https://github.com/grafana/grafana/issues/1703). Unsaved changes: Do not show for users with role `Viewer`

+ 12 - 0
pkg/api/render.go

@@ -13,6 +13,18 @@ import (
 func RenderToPng(c *middleware.Context) {
 func RenderToPng(c *middleware.Context) {
 	queryReader := util.NewUrlQueryReader(c.Req.URL)
 	queryReader := util.NewUrlQueryReader(c.Req.URL)
 	queryParams := fmt.Sprintf("?%s", c.Req.URL.RawQuery)
 	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{
 	renderOpts := &renderer.RenderOpts{
 		Url:       c.Params("*") + queryParams,
 		Url:       c.Params("*") + queryParams,

+ 2 - 9
pkg/components/renderer/renderer.go

@@ -1,8 +1,6 @@
 package renderer
 package renderer
 
 
 import (
 import (
-	"crypto/md5"
-	"encoding/hex"
 	"io"
 	"io"
 	"os"
 	"os"
 	"os/exec"
 	"os/exec"
@@ -11,6 +9,7 @@ import (
 
 
 	"github.com/grafana/grafana/pkg/log"
 	"github.com/grafana/grafana/pkg/log"
 	"github.com/grafana/grafana/pkg/setting"
 	"github.com/grafana/grafana/pkg/setting"
+	"github.com/grafana/grafana/pkg/util"
 )
 )
 
 
 type RenderOpts struct {
 type RenderOpts struct {
@@ -24,7 +23,7 @@ func RenderToPng(params *RenderOpts) (string, error) {
 	log.Info("PhantomRenderer::renderToPng url %v", params.Url)
 	log.Info("PhantomRenderer::renderToPng url %v", params.Url)
 	binPath, _ := filepath.Abs(filepath.Join(setting.PhantomDir, "phantomjs"))
 	binPath, _ := filepath.Abs(filepath.Join(setting.PhantomDir, "phantomjs"))
 	scriptPath, _ := filepath.Abs(filepath.Join(setting.PhantomDir, "render.js"))
 	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"
 	pngPath = pngPath + ".png"
 
 
 	cmd := exec.Command(binPath, scriptPath, "url="+params.Url, "width="+params.Width,
 	cmd := exec.Command(binPath, scriptPath, "url="+params.Url, "width="+params.Width,
@@ -64,9 +63,3 @@ func RenderToPng(params *RenderOpts) (string, error) {
 
 
 	return pngPath, nil
 	return pngPath, nil
 }
 }
-
-func getHash(text string) string {
-	hasher := md5.New()
-	hasher.Write([]byte(text))
-	return hex.EncodeToString(hasher.Sum(nil))
-}

+ 28 - 2
pkg/middleware/middleware.go

@@ -34,8 +34,14 @@ func GetContextHandler() macaron.Handler {
 			AllowAnonymous: false,
 			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) ||
 		if initContextWithApiKey(ctx) ||
 			initContextWithUserSessionCookie(ctx) ||
 			initContextWithUserSessionCookie(ctx) ||
+			initContextWithApiKeyFromSession(ctx) ||
 			initContextWithAnonymousUser(ctx) {
 			initContextWithAnonymousUser(ctx) {
 		}
 		}
 
 
@@ -77,7 +83,6 @@ func initContextWithUserSessionCookie(ctx *Context) bool {
 
 
 	query := m.GetSignedInUserQuery{UserId: userId}
 	query := m.GetSignedInUserQuery{UserId: userId}
 	if err := bus.Dispatch(&query); err != nil {
 	if err := bus.Dispatch(&query); err != nil {
-		log.Error(3, "Failed to get user by id, %v, %v", userId, err)
 		return false
 		return false
 	} else {
 	} else {
 		ctx.SignedInUser = query.Result
 		ctx.SignedInUser = query.Result
@@ -114,8 +119,29 @@ func initContextWithApiKey(ctx *Context) bool {
 
 
 		ctx.IsSignedIn = true
 		ctx.IsSignedIn = true
 		ctx.SignedInUser = &m.SignedInUser{}
 		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.OrgRole = apikey.Role
 		ctx.ApiKeyId = apikey.Id
 		ctx.ApiKeyId = apikey.Id
 		ctx.OrgId = apikey.OrgId
 		ctx.OrgId = apikey.OrgId

+ 6 - 3
pkg/middleware/session.go

@@ -11,8 +11,8 @@ import (
 )
 )
 
 
 const (
 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
 var sessionManager *session.Manager
@@ -102,7 +102,10 @@ func (s *SessionWrapper) Release() error {
 
 
 func (s *SessionWrapper) Destory(c *Context) error {
 func (s *SessionWrapper) Destory(c *Context) error {
 	if s.session != nil {
 	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
 	return nil
 }
 }

+ 5 - 0
pkg/models/apikey.go

@@ -55,6 +55,11 @@ type GetApiKeyByNameQuery struct {
 	Result  *ApiKey
 	Result  *ApiKey
 }
 }
 
 
+type GetApiKeyByIdQuery struct {
+	ApiKeyId int64
+	Result   *ApiKey
+}
+
 // ------------------------
 // ------------------------
 // DTO & Projections
 // DTO & Projections
 
 

+ 15 - 0
pkg/services/sqlstore/apikey.go

@@ -10,6 +10,7 @@ import (
 
 
 func init() {
 func init() {
 	bus.AddHandler("sql", GetApiKeys)
 	bus.AddHandler("sql", GetApiKeys)
+	bus.AddHandler("sql", GetApiKeyById)
 	bus.AddHandler("sql", GetApiKeyByName)
 	bus.AddHandler("sql", GetApiKeyByName)
 	bus.AddHandler("sql", DeleteApiKey)
 	bus.AddHandler("sql", DeleteApiKey)
 	bus.AddHandler("sql", AddApiKey)
 	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 {
 func GetApiKeyByName(query *m.GetApiKeyByNameQuery) error {
 	var apikey m.ApiKey
 	var apikey m.ApiKey
 	has, err := x.Where("org_id=? AND name=?", query.OrgId, query.KeyName).Get(&apikey)
 	has, err := x.Where("org_id=? AND name=?", query.OrgId, query.KeyName).Get(&apikey)

+ 4 - 0
pkg/setting/setting.go

@@ -259,6 +259,10 @@ func readSessionConfig() {
 	if SessionOptions.Provider == "file" {
 	if SessionOptions.Provider == "file" {
 		os.MkdirAll(path.Dir(SessionOptions.ProviderConfig), os.ModePerm)
 		os.MkdirAll(path.Dir(SessionOptions.ProviderConfig), os.ModePerm)
 	}
 	}
+
+	if SessionOptions.CookiePath == "" {
+		SessionOptions.CookiePath = "/"
+	}
 }
 }
 
 
 var logLevels = map[string]string{
 var logLevels = map[string]string{