소스 검색

dataproxy: refactoring data source proxy to support route templates and wrote more tests for data proxy code, #9078

Torkel Ödegaard 8 년 전
부모
커밋
8bf49c51b9
6개의 변경된 파일158개의 추가작업 그리고 120개의 파일을 삭제
  1. 1 2
      pkg/api/app_routes.go
  2. 9 1
      pkg/api/dataproxy.go
  3. 0 63
      pkg/api/dataproxy_test.go
  4. 23 16
      pkg/api/pluginproxy/ds_proxy.go
  5. 120 32
      pkg/api/pluginproxy/ds_proxy_test.go
  6. 5 6
      pkg/plugins/app_plugin.go

+ 1 - 2
pkg/api/app_routes.go

@@ -32,8 +32,7 @@ func InitAppPluginRoutes(r *macaron.Macaron) {
 			url := util.JoinUrlFragments("/api/plugin-proxy/"+plugin.Id, route.Path)
 			url := util.JoinUrlFragments("/api/plugin-proxy/"+plugin.Id, route.Path)
 			handlers := make([]macaron.Handler, 0)
 			handlers := make([]macaron.Handler, 0)
 			handlers = append(handlers, middleware.Auth(&middleware.AuthOptions{
 			handlers = append(handlers, middleware.Auth(&middleware.AuthOptions{
-				ReqSignedIn:     true,
-				ReqGrafanaAdmin: route.ReqGrafanaAdmin,
+				ReqSignedIn: true,
 			}))
 			}))
 
 
 			if route.ReqRole != "" {
 			if route.ReqRole != "" {

+ 9 - 1
pkg/api/dataproxy.go

@@ -6,6 +6,7 @@ import (
 	"github.com/grafana/grafana/pkg/metrics"
 	"github.com/grafana/grafana/pkg/metrics"
 	"github.com/grafana/grafana/pkg/middleware"
 	"github.com/grafana/grafana/pkg/middleware"
 	m "github.com/grafana/grafana/pkg/models"
 	m "github.com/grafana/grafana/pkg/models"
+	"github.com/grafana/grafana/pkg/plugins"
 )
 )
 
 
 func getDatasource(id int64, orgId int64) (*m.DataSource, error) {
 func getDatasource(id int64, orgId int64) (*m.DataSource, error) {
@@ -27,7 +28,14 @@ func ProxyDataSourceRequest(c *middleware.Context) {
 		return
 		return
 	}
 	}
 
 
+	// find plugin
+	plugin, ok := plugins.DataSources[ds.Type]
+	if !ok {
+		c.JsonApiErr(500, "Unable to find datasource plugin", err)
+		return
+	}
+
 	proxyPath := c.Params("*")
 	proxyPath := c.Params("*")
-	proxy := pluginproxy.NewDataSourceProxy(ds, c, proxyPath)
+	proxy := pluginproxy.NewDataSourceProxy(ds, plugin, c, proxyPath)
 	proxy.HandleRequest()
 	proxy.HandleRequest()
 }
 }

+ 0 - 63
pkg/api/dataproxy_test.go

@@ -1,63 +0,0 @@
-package api
-
-import (
-	"net/http"
-	"net/url"
-	"testing"
-
-	. "github.com/smartystreets/goconvey/convey"
-
-	m "github.com/grafana/grafana/pkg/models"
-)
-
-func TestDataSourceProxy(t *testing.T) {
-	Convey("When getting graphite datasource proxy", t, func() {
-		ds := m.DataSource{Url: "htttp://graphite:8080", Type: m.DS_GRAPHITE}
-		targetUrl, err := url.Parse(ds.Url)
-		proxy := NewReverseProxy(&ds, "/render", targetUrl)
-		proxy.Transport, err = ds.GetHttpTransport()
-		So(err, ShouldBeNil)
-
-		transport, ok := proxy.Transport.(*http.Transport)
-		So(ok, ShouldBeTrue)
-		So(transport.TLSClientConfig.InsecureSkipVerify, ShouldBeTrue)
-
-		requestUrl, _ := url.Parse("http://grafana.com/sub")
-		req := http.Request{URL: requestUrl}
-
-		proxy.Director(&req)
-
-		Convey("Can translate request url and path", func() {
-			So(req.URL.Host, ShouldEqual, "graphite:8080")
-			So(req.URL.Path, ShouldEqual, "/render")
-		})
-	})
-
-	Convey("When getting influxdb datasource proxy", t, func() {
-		ds := m.DataSource{
-			Type:     m.DS_INFLUXDB_08,
-			Url:      "http://influxdb:8083",
-			Database: "site",
-			User:     "user",
-			Password: "password",
-		}
-
-		targetUrl, _ := url.Parse(ds.Url)
-		proxy := NewReverseProxy(&ds, "", targetUrl)
-
-		requestUrl, _ := url.Parse("http://grafana.com/sub")
-		req := http.Request{URL: requestUrl}
-
-		proxy.Director(&req)
-
-		Convey("Should add db to url", func() {
-			So(req.URL.Path, ShouldEqual, "/db/site/")
-		})
-
-		Convey("Should add username and password", func() {
-			queryVals := req.URL.Query()
-			So(queryVals["u"][0], ShouldEqual, "user")
-			So(queryVals["p"][0], ShouldEqual, "password")
-		})
-	})
-}

+ 23 - 16
pkg/api/pluginproxy/ds_proxy.go

@@ -32,13 +32,18 @@ type DataSourceProxy struct {
 	targetUrl *url.URL
 	targetUrl *url.URL
 	proxyPath string
 	proxyPath string
 	route     *plugins.AppPluginRoute
 	route     *plugins.AppPluginRoute
+	plugin    *plugins.DataSourcePlugin
 }
 }
 
 
-func NewDataSourceProxy(ds *m.DataSource, ctx *middleware.Context, proxyPath string) *DataSourceProxy {
+func NewDataSourceProxy(ds *m.DataSource, plugin *plugins.DataSourcePlugin, ctx *middleware.Context, proxyPath string) *DataSourceProxy {
+	targetUrl, _ := url.Parse(ds.Url)
+
 	return &DataSourceProxy{
 	return &DataSourceProxy{
 		ds:        ds,
 		ds:        ds,
+		plugin:    plugin,
 		ctx:       ctx,
 		ctx:       ctx,
 		proxyPath: proxyPath,
 		proxyPath: proxyPath,
+		targetUrl: targetUrl,
 	}
 	}
 }
 }
 
 
@@ -142,8 +147,7 @@ func (proxy *DataSourceProxy) validateRequest() error {
 		}
 		}
 	}
 	}
 
 
-	targetUrl, _ := url.Parse(proxy.ds.Url)
-	if !checkWhiteList(proxy.ctx, targetUrl.Host) {
+	if !checkWhiteList(proxy.ctx, proxy.targetUrl.Host) {
 		return errors.New("Target url is not a valid target")
 		return errors.New("Target url is not a valid target")
 	}
 	}
 
 
@@ -166,25 +170,26 @@ func (proxy *DataSourceProxy) validateRequest() error {
 	}
 	}
 
 
 	// found route if there are any
 	// found route if there are any
-	if plugin, ok := plugins.DataSources[proxy.ds.Type]; ok {
-		if len(plugin.Routes) > 0 {
-			for _, route := range plugin.Routes {
-				// method match
-				if route.Method != "*" && route.Method != proxy.ctx.Req.Method {
-					continue
-				}
+	if len(proxy.plugin.Routes) > 0 {
+		for _, route := range proxy.plugin.Routes {
+			// method match
+			if route.Method != "" && route.Method != "*" && route.Method != proxy.ctx.Req.Method {
+				continue
+			}
 
 
-				if strings.HasPrefix(proxy.proxyPath, route.Path) {
-					logger.Info("Apply Route Rule", "rule", route.Path)
-					proxy.proxyPath = strings.TrimPrefix(proxy.proxyPath, route.Path)
-					proxy.route = route
-					break
+			if route.ReqRole.IsValid() {
+				if !proxy.ctx.HasUserRole(route.ReqRole) {
+					return errors.New("Plugin proxy route access denied")
 				}
 				}
 			}
 			}
+
+			if strings.HasPrefix(proxy.proxyPath, route.Path) {
+				proxy.route = route
+				break
+			}
 		}
 		}
 	}
 	}
 
 
-	proxy.targetUrl = targetUrl
 	return nil
 	return nil
 }
 }
 
 
@@ -226,6 +231,8 @@ func checkWhiteList(c *middleware.Context, host string) bool {
 func (proxy *DataSourceProxy) applyRoute(req *http.Request) {
 func (proxy *DataSourceProxy) applyRoute(req *http.Request) {
 	logger.Info("ApplyDataSourceRouteRules", "route", proxy.route.Path, "proxyPath", proxy.proxyPath)
 	logger.Info("ApplyDataSourceRouteRules", "route", proxy.route.Path, "proxyPath", proxy.proxyPath)
 
 
+	proxy.proxyPath = strings.TrimPrefix(proxy.proxyPath, proxy.route.Path)
+
 	data := templateData{
 	data := templateData{
 		JsonData:       proxy.ds.JsonData.Interface().(map[string]interface{}),
 		JsonData:       proxy.ds.JsonData.Interface().(map[string]interface{}),
 		SecureJsonData: proxy.ds.SecureJsonData.Decrypt(),
 		SecureJsonData: proxy.ds.SecureJsonData.Decrypt(),

+ 120 - 32
pkg/api/pluginproxy/ds_proxy_test.go

@@ -2,9 +2,13 @@ package pluginproxy
 
 
 import (
 import (
 	"net/http"
 	"net/http"
+	"net/url"
 	"testing"
 	"testing"
 
 
+	macaron "gopkg.in/macaron.v1"
+
 	"github.com/grafana/grafana/pkg/components/simplejson"
 	"github.com/grafana/grafana/pkg/components/simplejson"
+	"github.com/grafana/grafana/pkg/middleware"
 	m "github.com/grafana/grafana/pkg/models"
 	m "github.com/grafana/grafana/pkg/models"
 	"github.com/grafana/grafana/pkg/plugins"
 	"github.com/grafana/grafana/pkg/plugins"
 	"github.com/grafana/grafana/pkg/setting"
 	"github.com/grafana/grafana/pkg/setting"
@@ -14,51 +18,135 @@ import (
 
 
 func TestDSRouteRule(t *testing.T) {
 func TestDSRouteRule(t *testing.T) {
 
 
-	Convey("When applying ds route rule", t, func() {
-		plugin := &plugins.DataSourcePlugin{
-			Routes: []*plugins.AppPluginRoute{
-				{
-					Path: "api/v4/",
-					Url:  "https://www.google.com",
-					Headers: []plugins.AppPluginRouteHeader{
-						{Name: "x-header", Content: "my secret {{.SecureJsonData.key}}"},
+	Convey("DataSourceProxy", t, func() {
+		Convey("Plugin with routes", func() {
+			plugin := &plugins.DataSourcePlugin{
+				Routes: []*plugins.AppPluginRoute{
+					{
+						Path:    "api/v4/",
+						Url:     "https://www.google.com",
+						ReqRole: m.ROLE_EDITOR,
+						Headers: []plugins.AppPluginRouteHeader{
+							{Name: "x-header", Content: "my secret {{.SecureJsonData.key}}"},
+						},
+					},
+					{
+						Path:    "api/admin",
+						Url:     "https://www.google.com",
+						ReqRole: m.ROLE_ADMIN,
+						Headers: []plugins.AppPluginRouteHeader{
+							{Name: "x-header", Content: "my secret {{.SecureJsonData.key}}"},
+						},
+					},
+					{
+						Path: "api/anon",
+						Url:  "https://www.google.com",
+						Headers: []plugins.AppPluginRouteHeader{
+							{Name: "x-header", Content: "my secret {{.SecureJsonData.key}}"},
+						},
 					},
 					},
 				},
 				},
-			},
-		}
+			}
 
 
-		setting.SecretKey = "password"
-		key, _ := util.Encrypt([]byte("123"), "password")
+			setting.SecretKey = "password"
+			key, _ := util.Encrypt([]byte("123"), "password")
 
 
-		ds := &m.DataSource{
-			JsonData: simplejson.NewFromAny(map[string]interface{}{
-				"clientId": "asd",
-			}),
-			SecureJsonData: map[string][]byte{
-				"key": key,
-			},
-		}
+			ds := &m.DataSource{
+				JsonData: simplejson.NewFromAny(map[string]interface{}{
+					"clientId": "asd",
+				}),
+				SecureJsonData: map[string][]byte{
+					"key": key,
+				},
+			}
 
 
-		req, _ := http.NewRequest("GET", "http://localhost/asd", nil)
+			req, _ := http.NewRequest("GET", "http://localhost/asd", nil)
+			ctx := &middleware.Context{
+				Context: &macaron.Context{
+					Req: macaron.Request{Request: req},
+				},
+				SignedInUser: &m.SignedInUser{OrgRole: m.ROLE_EDITOR},
+			}
 
 
-		Convey("When not matching route path", func() {
-			ApplyDataSourceRouteRules(req, plugin, ds, "/asdas/asd")
+			Convey("When matching route path", func() {
+				proxy := NewDataSourceProxy(ds, plugin, ctx, "api/v4/some/method")
+				proxy.route = plugin.Routes[0]
+				proxy.applyRoute(req)
 
 
-			Convey("should not touch req", func() {
-				So(len(req.Header), ShouldEqual, 0)
-				So(req.URL.String(), ShouldEqual, "http://localhost/asd")
+				Convey("should add headers and update url", func() {
+					So(req.URL.String(), ShouldEqual, "https://www.google.com/some/method")
+					So(req.Header.Get("x-header"), ShouldEqual, "my secret 123")
+				})
+			})
+
+			Convey("Validating request", func() {
+				Convey("plugin route with valid role", func() {
+					proxy := NewDataSourceProxy(ds, plugin, ctx, "api/v4/some/method")
+					err := proxy.validateRequest()
+					So(err, ShouldBeNil)
+				})
+
+				Convey("plugin route with admin role and user is editor", func() {
+					proxy := NewDataSourceProxy(ds, plugin, ctx, "api/admin")
+					err := proxy.validateRequest()
+					So(err, ShouldNotBeNil)
+				})
+
+				Convey("plugin route with admin role and user is admin", func() {
+					ctx.SignedInUser.OrgRole = m.ROLE_ADMIN
+					proxy := NewDataSourceProxy(ds, plugin, ctx, "api/admin")
+					err := proxy.validateRequest()
+					So(err, ShouldBeNil)
+				})
 			})
 			})
 		})
 		})
 
 
-		Convey("When matching route path", func() {
-			ApplyDataSourceRouteRules(req, plugin, ds, "api/v4/some/method")
+		Convey("When proxying graphite", func() {
+			plugin := &plugins.DataSourcePlugin{}
+			ds := &m.DataSource{Url: "htttp://graphite:8080", Type: m.DS_GRAPHITE}
+			ctx := &middleware.Context{}
+
+			proxy := NewDataSourceProxy(ds, plugin, ctx, "/render")
+
+			requestUrl, _ := url.Parse("http://grafana.com/sub")
+			req := http.Request{URL: requestUrl}
 
 
-			Convey("should add headers and update url", func() {
-				So(req.URL.String(), ShouldEqual, "https://www.google.com/some/method")
-				So(req.Header.Get("x-header"), ShouldEqual, "my secret 123")
+			proxy.getDirector()(&req)
+
+			Convey("Can translate request url and path", func() {
+				So(req.URL.Host, ShouldEqual, "graphite:8080")
+				So(req.URL.Path, ShouldEqual, "/render")
 			})
 			})
 		})
 		})
 
 
-	})
+		Convey("When proxying InfluxDB", func() {
+			plugin := &plugins.DataSourcePlugin{}
+
+			ds := &m.DataSource{
+				Type:     m.DS_INFLUXDB_08,
+				Url:      "http://influxdb:8083",
+				Database: "site",
+				User:     "user",
+				Password: "password",
+			}
+
+			ctx := &middleware.Context{}
+			proxy := NewDataSourceProxy(ds, plugin, ctx, "")
+
+			requestUrl, _ := url.Parse("http://grafana.com/sub")
+			req := http.Request{URL: requestUrl}
+
+			proxy.getDirector()(&req)
 
 
+			Convey("Should add db to url", func() {
+				So(req.URL.Path, ShouldEqual, "/db/site/")
+			})
+
+			Convey("Should add username and password", func() {
+				queryVals := req.URL.Query()
+				So(queryVals["u"][0], ShouldEqual, "user")
+				So(queryVals["p"][0], ShouldEqual, "password")
+			})
+		})
+	})
 }
 }

+ 5 - 6
pkg/plugins/app_plugin.go

@@ -23,12 +23,11 @@ type AppPlugin struct {
 }
 }
 
 
 type AppPluginRoute struct {
 type AppPluginRoute struct {
-	Path            string                 `json:"path"`
-	Method          string                 `json:"method"`
-	ReqGrafanaAdmin bool                   `json:"reqGrafanaAdmin"`
-	ReqRole         models.RoleType        `json:"reqRole"`
-	Url             string                 `json:"url"`
-	Headers         []AppPluginRouteHeader `json:"headers"`
+	Path    string                 `json:"path"`
+	Method  string                 `json:"method"`
+	ReqRole models.RoleType        `json:"reqRole"`
+	Url     string                 `json:"url"`
+	Headers []AppPluginRouteHeader `json:"headers"`
 }
 }
 
 
 type AppPluginRouteHeader struct {
 type AppPluginRouteHeader struct {