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

This commit is contained in:
Torkel Ödegaard 2015-04-08 08:59:12 +02:00
parent f28af4f369
commit 059db533d5
8 changed files with 73 additions and 14 deletions

View File

@ -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`

View File

@ -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,

View File

@ -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))
}

View File

@ -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
}
}
// TODO: fix this
// 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
ctx.IsSignedIn = true
ctx.SignedInUser = &m.SignedInUser{}
ctx.OrgRole = apikey.Role
ctx.ApiKeyId = apikey.Id
ctx.OrgId = apikey.OrgId

View File

@ -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
}

View File

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

View File

@ -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)

View File

@ -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{