mirror of
				https://github.com/grafana/grafana.git
				synced 2025-02-25 18:55:37 -06:00 
			
		
		
		
	fix(server side rendering): Fixed issues with server side rendering for alerting & for auth proxy scenarios, fixes #6115, fixes #5906
This commit is contained in:
		@@ -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{}{
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
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 {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										55
									
								
								pkg/middleware/render_auth.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								pkg/middleware/render_auth.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,55 @@
 | 
			
		||||
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 {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user