浏览代码

feat(app routes): worked on app routes, added unit test, changed Grafana-Context header to start with X to be standard compliant, got cloud saas queries to work via app route feature and header template

Torkel Ödegaard 10 年之前
父节点
当前提交
37c6a1ddf0

+ 10 - 80
pkg/api/app_routes.go

@@ -1,17 +1,9 @@
 package api
 
 import (
-	"bytes"
-	"encoding/json"
-	"fmt"
-	"net/http"
-	"net/http/httputil"
-	"net/url"
-	"text/template"
-
 	"gopkg.in/macaron.v1"
 
-	"github.com/grafana/grafana/pkg/bus"
+	"github.com/grafana/grafana/pkg/api/pluginproxy"
 	"github.com/grafana/grafana/pkg/log"
 	"github.com/grafana/grafana/pkg/middleware"
 	m "github.com/grafana/grafana/pkg/models"
@@ -22,16 +14,14 @@ import (
 func InitAppPluginRoutes(r *macaron.Macaron) {
 	for _, plugin := range plugins.Apps {
 		for _, route := range plugin.Routes {
-			log.Info("Plugin: Adding proxy route for app plugin")
-			url := util.JoinUrlFragments("/api/plugin-proxy/", route.Path)
+			url := util.JoinUrlFragments("/api/plugin-proxy/"+plugin.Id, route.Path)
 			handlers := make([]macaron.Handler, 0)
-			if route.ReqSignedIn {
-				handlers = append(handlers, middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true}))
-			}
-			if route.ReqGrafanaAdmin {
-				handlers = append(handlers, middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true, ReqGrafanaAdmin: true}))
-			}
-			if route.ReqSignedIn && route.ReqRole != "" {
+			handlers = append(handlers, middleware.Auth(&middleware.AuthOptions{
+				ReqSignedIn:     true,
+				ReqGrafanaAdmin: route.ReqGrafanaAdmin,
+			}))
+
+			if route.ReqRole != "" {
 				if route.ReqRole == m.ROLE_ADMIN {
 					handlers = append(handlers, middleware.RoleAuth(m.ROLE_ADMIN))
 				} else if route.ReqRole == m.ROLE_EDITOR {
@@ -40,7 +30,7 @@ func InitAppPluginRoutes(r *macaron.Macaron) {
 			}
 			handlers = append(handlers, AppPluginRoute(route, plugin.Id))
 			r.Route(url, route.Method, handlers...)
-			log.Info("Plugin: Adding route %s", url)
+			log.Info("Plugins: Adding proxy route %s", url)
 		}
 	}
 }
@@ -49,68 +39,8 @@ func AppPluginRoute(route *plugins.AppPluginRoute, appId string) macaron.Handler
 	return func(c *middleware.Context) {
 		path := c.Params("*")
 
-		proxy := NewApiPluginProxy(c, path, route, appId)
+		proxy := pluginproxy.NewApiPluginProxy(c, path, route, appId)
 		proxy.Transport = dataProxyTransport
 		proxy.ServeHTTP(c.Resp, c.Req.Request)
 	}
 }
-
-func NewApiPluginProxy(ctx *middleware.Context, proxyPath string, route *plugins.AppPluginRoute, appId string) *httputil.ReverseProxy {
-	targetUrl, _ := url.Parse(route.Url)
-
-	director := func(req *http.Request) {
-
-		req.URL.Scheme = targetUrl.Scheme
-		req.URL.Host = targetUrl.Host
-		req.Host = targetUrl.Host
-
-		req.URL.Path = util.JoinUrlFragments(targetUrl.Path, proxyPath)
-
-		// clear cookie headers
-		req.Header.Del("Cookie")
-		req.Header.Del("Set-Cookie")
-
-		//Create a HTTP header with the context in it.
-		ctxJson, err := json.Marshal(ctx.SignedInUser)
-		if err != nil {
-			ctx.JsonApiErr(500, "failed to marshal context to json.", err)
-			return
-		}
-
-		req.Header.Add("Grafana-Context", string(ctxJson))
-		// add custom headers defined in the plugin config.
-		for _, header := range route.Headers {
-			var contentBuf bytes.Buffer
-			t, err := template.New("content").Parse(header.Content)
-			if err != nil {
-				ctx.JsonApiErr(500, fmt.Sprintf("could not parse header content template for header %s.", header.Name), err)
-				return
-			}
-
-			//lookup appSettings
-			query := m.GetAppSettingByAppIdQuery{OrgId: ctx.OrgId, AppId: appId}
-
-			if err := bus.Dispatch(&query); err != nil {
-				ctx.JsonApiErr(500, "failed to get AppSettings.", err)
-				return
-			}
-			type templateData struct {
-				JsonData       map[string]interface{}
-				SecureJsonData map[string]string
-			}
-			data := templateData{
-				JsonData:       query.Result.JsonData,
-				SecureJsonData: query.Result.SecureJsonData.Decrypt(),
-			}
-			err = t.Execute(&contentBuf, data)
-			if err != nil {
-				ctx.JsonApiErr(500, fmt.Sprintf("failed to execute header content template for header %s.", header.Name), err)
-				return
-			}
-			log.Debug("Adding header to proxy request. %s: %s", header.Name, contentBuf.String())
-			req.Header.Add(header.Name, contentBuf.String())
-		}
-	}
-
-	return &httputil.ReverseProxy{Director: director}
-}

+ 99 - 0
pkg/api/pluginproxy/pluginproxy.go

@@ -0,0 +1,99 @@
+package pluginproxy
+
+import (
+	"bytes"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"net/http"
+	"net/http/httputil"
+	"net/url"
+	"text/template"
+
+	"github.com/grafana/grafana/pkg/bus"
+	"github.com/grafana/grafana/pkg/log"
+	"github.com/grafana/grafana/pkg/middleware"
+	m "github.com/grafana/grafana/pkg/models"
+	"github.com/grafana/grafana/pkg/plugins"
+	"github.com/grafana/grafana/pkg/util"
+)
+
+type templateData struct {
+	JsonData       map[string]interface{}
+	SecureJsonData map[string]string
+}
+
+func getHeaders(route *plugins.AppPluginRoute, orgId int64, appId string) (http.Header, error) {
+	result := http.Header{}
+
+	query := m.GetAppSettingByAppIdQuery{OrgId: orgId, AppId: appId}
+
+	if err := bus.Dispatch(&query); err != nil {
+		return nil, err
+	}
+
+	data := templateData{
+		JsonData:       query.Result.JsonData,
+		SecureJsonData: query.Result.SecureJsonData.Decrypt(),
+	}
+
+	for _, header := range route.Headers {
+		var contentBuf bytes.Buffer
+		t, err := template.New("content").Parse(header.Content)
+		if err != nil {
+			return nil, errors.New(fmt.Sprintf("could not parse header content template for header %s.", header.Name))
+		}
+
+		err = t.Execute(&contentBuf, data)
+		if err != nil {
+			return nil, errors.New(fmt.Sprintf("failed to execute header content template for header %s.", header.Name))
+		}
+
+		log.Trace("Adding header to proxy request. %s: %s", header.Name, contentBuf.String())
+		result.Add(header.Name, contentBuf.String())
+	}
+
+	return result, nil
+}
+
+func NewApiPluginProxy(ctx *middleware.Context, proxyPath string, route *plugins.AppPluginRoute, appId string) *httputil.ReverseProxy {
+	targetUrl, _ := url.Parse(route.Url)
+
+	director := func(req *http.Request) {
+
+		req.URL.Scheme = targetUrl.Scheme
+		req.URL.Host = targetUrl.Host
+		req.Host = targetUrl.Host
+
+		req.URL.Path = util.JoinUrlFragments(targetUrl.Path, proxyPath)
+
+		// clear cookie headers
+		req.Header.Del("Cookie")
+		req.Header.Del("Set-Cookie")
+
+		//Create a HTTP header with the context in it.
+		ctxJson, err := json.Marshal(ctx.SignedInUser)
+		if err != nil {
+			ctx.JsonApiErr(500, "failed to marshal context to json.", err)
+			return
+		}
+
+		req.Header.Add("X-Grafana-Context", string(ctxJson))
+
+		if len(route.Headers) > 0 {
+			headers, err := getHeaders(route, ctx.OrgId, appId)
+			if err != nil {
+				ctx.JsonApiErr(500, "Could not generate plugin route header", err)
+				return
+			}
+
+			for key, value := range headers {
+				log.Info("setting key %v value %v", key, value[0])
+				req.Header.Set(key, value[0])
+			}
+		}
+
+	}
+
+	return &httputil.ReverseProxy{Director: director}
+}

+ 42 - 0
pkg/api/pluginproxy/pluginproxy_test.go

@@ -0,0 +1,42 @@
+package pluginproxy
+
+import (
+	"testing"
+
+	"github.com/grafana/grafana/pkg/bus"
+	m "github.com/grafana/grafana/pkg/models"
+	"github.com/grafana/grafana/pkg/plugins"
+	"github.com/grafana/grafana/pkg/setting"
+	"github.com/grafana/grafana/pkg/util"
+	. "github.com/smartystreets/goconvey/convey"
+)
+
+func TestPluginProxy(t *testing.T) {
+
+	Convey("When getting proxy headers", t, func() {
+		route := &plugins.AppPluginRoute{
+			Headers: []plugins.AppPluginRouteHeader{
+				{Name: "x-header", Content: "my secret {{.SecureJsonData.key}}"},
+			},
+		}
+
+		setting.SecretKey = "password"
+
+		bus.AddHandler("test", func(query *m.GetAppSettingByAppIdQuery) error {
+			query.Result = &m.AppSettings{
+				SecureJsonData: map[string][]byte{
+					"key": util.Encrypt([]byte("123"), "password"),
+				},
+			}
+			return nil
+		})
+
+		header, err := getHeaders(route, 1, "my-app")
+		So(err, ShouldBeNil)
+
+		Convey("Should render header template", func() {
+			So(header.Get("x-header"), ShouldEqual, "my secret 123")
+		})
+	})
+
+}

+ 8 - 0
pkg/models/app_settings.go

@@ -49,6 +49,14 @@ type UpdateAppSettingsCmd struct {
 	OrgId int64  `json:"-"`
 }
 
+func (cmd *UpdateAppSettingsCmd) GetEncryptedJsonData() SecureJsonData {
+	encrypted := make(SecureJsonData)
+	for key, data := range cmd.SecureJsonData {
+		encrypted[key] = util.Encrypt([]byte(data), setting.SecretKey)
+	}
+	return encrypted
+}
+
 // ---------------------
 // QUERIES
 type GetAppSettingsQuery struct {

+ 0 - 1
pkg/plugins/app_plugin.go

@@ -39,7 +39,6 @@ type AppPlugin struct {
 type AppPluginRoute struct {
 	Path            string                 `json:"path"`
 	Method          string                 `json:"method"`
-	ReqSignedIn     bool                   `json:"reqSignedIn"`
 	ReqGrafanaAdmin bool                   `json:"reqGrafanaAdmin"`
 	ReqRole         models.RoleType        `json:"reqRole"`
 	Url             string                 `json:"url"`

+ 2 - 6
pkg/services/sqlstore/app_settings.go

@@ -42,18 +42,13 @@ func UpdateAppSettings(cmd *m.UpdateAppSettingsCmd) error {
 		sess.UseBool("enabled")
 		sess.UseBool("pinned")
 		if !exists {
-			// encrypt secureJsonData
-			secureJsonData := make(map[string][]byte)
-			for key, data := range cmd.SecureJsonData {
-				secureJsonData[key] = util.Encrypt([]byte(data), setting.SecretKey)
-			}
 			app = m.AppSettings{
 				AppId:          cmd.AppId,
 				OrgId:          cmd.OrgId,
 				Enabled:        cmd.Enabled,
 				Pinned:         cmd.Pinned,
 				JsonData:       cmd.JsonData,
-				SecureJsonData: secureJsonData,
+				SecureJsonData: cmd.GetEncryptedJsonData(),
 				Created:        time.Now(),
 				Updated:        time.Now(),
 			}
@@ -63,6 +58,7 @@ func UpdateAppSettings(cmd *m.UpdateAppSettingsCmd) error {
 			for key, data := range cmd.SecureJsonData {
 				app.SecureJsonData[key] = util.Encrypt([]byte(data), setting.SecretKey)
 			}
+			app.SecureJsonData = cmd.GetEncryptedJsonData()
 			app.Updated = time.Now()
 			app.Enabled = cmd.Enabled
 			app.JsonData = cmd.JsonData