Commit 175c651e by Torkel Ödegaard

fix(server side rendering): Fixed issues with server side rendering for alerting…

fix(server side rendering): Fixed issues with server side rendering for alerting & for auth proxy scenarios, fixes #6115, fixes #5906
parent bb77caa3
......@@ -25,6 +25,7 @@
* **Graph panel**: Fixed problem with auto decimals on y axis when datamin=datamax, fixes [#6070](https://github.com/grafana/grafana/pull/6070)
* **Snapshot**: Can view embedded panels/png rendered panels in snapshots without login, fixes [#3769](https://github.com/grafana/grafana/pull/3769)
* **Elasticsearch**: Fix for query template variable when looking up terms without query, no longer relies on elasticsearch default field, fixes [#3887](https://github.com/grafana/grafana/pull/3887)
* **PNG Rendering**: Fix for server side rendering when using auth proxy, fixes [#5906](https://github.com/grafana/grafana/pull/5906)
# 3.1.2 (unreleased)
* **Templating**: Fixed issue when combining row & panel repeats, fixes [#5790](https://github.com/grafana/grafana/issues/5790)
......
......@@ -38,7 +38,7 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro
url := ds.Url
if ds.Access == m.DS_ACCESS_PROXY {
url = setting.AppSubUrl + "/api/datasources/proxy/" + strconv.FormatInt(ds.Id, 10)
url = "/api/datasources/proxy/" + strconv.FormatInt(ds.Id, 10)
}
var dsMap = map[string]interface{}{
......
package api
import (
"fmt"
"strings"
"github.com/grafana/grafana/pkg/api/dtos"
......@@ -32,6 +33,16 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
locale = parts[0]
}
appUrl := setting.AppUrl
appSubUrl := setting.AppSubUrl
// special case when doing localhost call from phantomjs
if c.IsRenderCall {
appUrl = fmt.Sprintf("%s://localhost:%s", setting.Protocol, setting.HttpPort)
appSubUrl = ""
settings["appSubUrl"] = ""
}
var data = dtos.IndexViewData{
User: &dtos.CurrentUser{
Id: c.UserId,
......@@ -49,8 +60,8 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) {
Locale: locale,
},
Settings: settings,
AppUrl: setting.AppUrl,
AppSubUrl: setting.AppSubUrl,
AppUrl: appUrl,
AppSubUrl: appSubUrl,
GoogleAnalyticsId: setting.GoogleAnalyticsId,
GoogleTagManagerId: setting.GoogleTagManagerId,
BuildVersion: setting.BuildVersion,
......
......@@ -6,35 +6,21 @@ import (
"github.com/grafana/grafana/pkg/components/renderer"
"github.com/grafana/grafana/pkg/middleware"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
)
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,
Width: queryReader.Get("width", "800"),
Height: queryReader.Get("height", "400"),
SessionId: c.Session.ID(),
Timeout: queryReader.Get("timeout", "30"),
Url: c.Params("*") + queryParams,
Width: queryReader.Get("width", "800"),
Height: queryReader.Get("height", "400"),
OrgId: c.OrgId,
Timeout: queryReader.Get("timeout", "30"),
}
renderOpts.Url = setting.ToAbsUrl(renderOpts.Url)
pngPath, err := renderer.RenderToPng(renderOpts)
if err != nil {
......
......@@ -12,16 +12,17 @@ import (
"strconv"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/middleware"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
)
type RenderOpts struct {
Url string
Width string
Height string
SessionId string
Timeout string
Url string
Width string
Height string
Timeout string
OrgId int64
}
var rendererLog log.Logger = log.New("png-renderer")
......@@ -34,14 +35,28 @@ func RenderToPng(params *RenderOpts) (string, error) {
executable = executable + ".exe"
}
params.Url = fmt.Sprintf("%s://localhost:%s/%s", setting.Protocol, setting.HttpPort, params.Url)
binPath, _ := filepath.Abs(filepath.Join(setting.PhantomDir, executable))
scriptPath, _ := filepath.Abs(filepath.Join(setting.PhantomDir, "render.js"))
pngPath, _ := filepath.Abs(filepath.Join(setting.ImagesDir, util.GetRandomString(20)))
pngPath = pngPath + ".png"
cmd := exec.Command(binPath, "--ignore-ssl-errors=true", scriptPath, "url="+params.Url, "width="+params.Width,
"height="+params.Height, "png="+pngPath, "cookiename="+setting.SessionOptions.CookieName,
"domain="+setting.Domain, "sessionid="+params.SessionId)
renderKey := middleware.AddRenderAuthKey(params.OrgId)
defer middleware.RemoveRenderAuthKey(renderKey)
cmdArgs := []string{
"--ignore-ssl-errors=true",
scriptPath,
"url=" + params.Url,
"width=" + params.Width,
"height=" + params.Height,
"png=" + pngPath,
"domain=" + setting.Domain,
"renderKey=" + renderKey,
}
cmd := exec.Command(binPath, cmdArgs...)
stdout, err := cmd.StdoutPipe()
if err != nil {
......
......@@ -22,6 +22,7 @@ type Context struct {
Session SessionStore
IsSignedIn bool
IsRenderCall bool
AllowAnonymous bool
Logger log.Logger
}
......@@ -42,11 +43,11 @@ func GetContextHandler() macaron.Handler {
// 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 initContextWithRenderAuth(ctx) ||
initContextWithApiKey(ctx) ||
initContextWithBasicAuth(ctx) ||
initContextWithAuthProxy(ctx) ||
initContextWithUserSessionCookie(ctx) ||
initContextWithApiKeyFromSession(ctx) ||
initContextWithAnonymousUser(ctx) {
}
......@@ -176,29 +177,6 @@ func initContextWithBasicAuth(ctx *Context) bool {
}
}
// 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 {
ctx.Logger.Error("Failed to get api key by id", "id", keyId, "error", 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
return true
}
}
// Handle handles and logs error by given status.
func (ctx *Context) Handle(status int, title string, err error) {
if err != nil {
......
package middleware
import (
"sync"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/util"
)
var renderKeysLock sync.Mutex
var renderKeys map[string]*m.SignedInUser = make(map[string]*m.SignedInUser)
func initContextWithRenderAuth(ctx *Context) bool {
key := ctx.GetCookie("renderKey")
if key == "" {
return false
}
renderKeysLock.Lock()
defer renderKeysLock.Unlock()
if renderUser, exists := renderKeys[key]; !exists {
ctx.JsonApiErr(401, "Invalid Render Key", nil)
return true
} else {
ctx.IsSignedIn = true
ctx.SignedInUser = renderUser
ctx.IsRenderCall = true
return true
}
}
type renderContextFunc func(key string) (string, error)
func AddRenderAuthKey(orgId int64) string {
renderKeysLock.Lock()
key := util.GetRandomString(32)
renderKeys[key] = &m.SignedInUser{
OrgId: orgId,
OrgRole: m.ROLE_VIEWER,
}
renderKeysLock.Unlock()
return key
}
func RemoveRenderAuthKey(key string) {
renderKeysLock.Lock()
delete(renderKeys, key)
renderKeysLock.Unlock()
}
......@@ -13,7 +13,6 @@ import (
const (
SESS_KEY_USERID = "uid"
SESS_KEY_APIKEY = "apikey_id" // used fror render requests with api keys
)
var sessionManager *session.Manager
......
......@@ -69,11 +69,11 @@ func (n *RootNotifier) uploadImage(context *EvalContext) error {
}
renderOpts := &renderer.RenderOpts{
Url: imageUrl,
Width: "800",
Height: "400",
SessionId: "cef0256d482b4293",
Timeout: "30",
Url: imageUrl,
Width: "800",
Height: "400",
Timeout: "30",
OrgId: context.Rule.OrgId,
}
if imagePath, err := renderer.RenderToPng(renderOpts); err != nil {
......
......@@ -114,6 +114,10 @@ export class BackendSrv {
var requestIsLocal = options.url.indexOf('/') === 0;
var firstAttempt = options.retry === 0;
if (requestIsLocal && !options.hasSubUrl && options.retry === 0) {
options.url = config.appSubUrl + options.url;
}
if (requestIsLocal && options.headers && options.headers.Authorization) {
options.headers['X-DS-Authorization'] = options.headers.Authorization;
delete options.headers.Authorization;
......
......@@ -12,17 +12,17 @@
params[parts[1]] = parts[2];
});
var usage = "url=<url> png=<filename> width=<width> height=<height> cookiename=<cookiename> sessionid=<sessionid> domain=<domain>";
var usage = "url=<url> png=<filename> width=<width> height=<height> renderKey=<key>";
if (!params.url || !params.png || !params.cookiename || ! params.sessionid || !params.domain) {
if (!params.url || !params.png || !params.renderKey || !params.domain) {
console.log(usage);
phantom.exit();
}
phantom.addCookie({
'name': params.cookiename,
'value': params.sessionid,
'domain': params.domain
'name': 'renderKey',
'value': params.renderKey,
'domain': 'localhost',
});
page.viewportSize = {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment