dsproxy: adds support for url params for plugin routes (#23503)

* dsproxy: adds support for url params for plugin routes

* docs: fixes after review

* pluginproxy: rename Params to URLParams

* Update pkg/plugins/app_plugin.go

Co-Authored-By: Arve Knudsen <arve.knudsen@gmail.com>

* Apply suggestions from code review

Co-Authored-By: Diana Payton <52059945+oddlittlebird@users.noreply.github.com>
Co-Authored-By: Arve Knudsen <arve.knudsen@gmail.com>

* pluginproxy: rename struct

Co-authored-by: Arve Knudsen <arve.knudsen@gmail.com>
Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com>
This commit is contained in:
Daniel Lee
2020-04-24 10:32:13 +02:00
committed by GitHub
parent 59bea141f2
commit 52154b465b
8 changed files with 138 additions and 85 deletions
@@ -3,13 +3,14 @@ package pluginproxy
import (
"context"
"encoding/json"
"github.com/stretchr/testify/require"
"net/http"
"net/http/httptest"
"strconv"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
. "github.com/smartystreets/goconvey/convey"
@@ -25,7 +26,7 @@ func TestAccessToken(t *testing.T) {
Convey("Plugin with JWT token auth route", t, func() {
pluginRoute := &plugins.AppPluginRoute{
Path: "pathwithjwttoken1",
Url: "https://api.jwt.io/some/path",
URL: "https://api.jwt.io/some/path",
Method: "GET",
JwtTokenAuth: &plugins.JwtTokenAuth{
Url: "https://login.server.com/{{.JsonData.tenantId}}/oauth2/token",
@@ -108,7 +109,7 @@ func TestAccessToken(t *testing.T) {
pluginRoute := &plugins.AppPluginRoute{
Path: "pathwithtokenauth1",
Url: "",
URL: "",
Method: "GET",
TokenAuth: &plugins.JwtTokenAuth{
Url: server.URL + "/oauth/token",
+25 -1
View File
@@ -22,7 +22,7 @@ func ApplyRoute(ctx context.Context, req *http.Request, proxyPath string, route
SecureJsonData: ds.SecureJsonData.Decrypt(),
}
interpolatedURL, err := InterpolateString(route.Url, data)
interpolatedURL, err := InterpolateString(route.URL, data)
if err != nil {
logger.Error("Error interpolating proxy url", "error", err)
return
@@ -39,6 +39,10 @@ func ApplyRoute(ctx context.Context, req *http.Request, proxyPath string, route
req.Host = routeURL.Host
req.URL.Path = util.JoinURLFragments(routeURL.Path, proxyPath)
if err := addQueryString(req, route, data); err != nil {
logger.Error("Failed to render plugin URL query string", "error", err)
}
if err := addHeaders(&req.Header, route, data); err != nil {
logger.Error("Failed to render plugin headers", "error", err)
}
@@ -79,6 +83,26 @@ func ApplyRoute(ctx context.Context, req *http.Request, proxyPath string, route
logger.Info("Requesting", "url", req.URL.String())
}
func addQueryString(req *http.Request, route *plugins.AppPluginRoute, data templateData) error {
q := req.URL.Query()
for _, param := range route.URLParams {
interpolatedName, err := InterpolateString(param.Name, data)
if err != nil {
return err
}
interpolatedContent, err := InterpolateString(param.Content, data)
if err != nil {
return err
}
q.Add(interpolatedName, interpolatedContent)
}
req.URL.RawQuery = q.Encode()
return nil
}
func addHeaders(reqHeaders *http.Header, route *plugins.AppPluginRoute, data templateData) error {
for _, header := range route.Headers {
interpolated, err := InterpolateString(header.Content, data)
+1
View File
@@ -187,6 +187,7 @@ func (proxy *DataSourceProxy) getDirector() func(req *http.Request) {
} else {
req.URL.Path = util.JoinURLFragments(proxy.targetUrl.Path, proxy.proxyPath)
}
if proxy.ds.BasicAuth {
req.Header.Del("Authorization")
req.Header.Add("Authorization", util.GetBasicAuthHeader(proxy.ds.BasicAuthUser, proxy.ds.DecryptedBasicAuthPassword()))
+12 -8
View File
@@ -36,7 +36,7 @@ func TestDSRouteRule(t *testing.T) {
Routes: []*plugins.AppPluginRoute{
{
Path: "api/v4/",
Url: "https://www.google.com",
URL: "https://www.google.com",
ReqRole: models.ROLE_EDITOR,
Headers: []plugins.AppPluginRouteHeader{
{Name: "x-header", Content: "my secret {{.SecureJsonData.key}}"},
@@ -44,7 +44,7 @@ func TestDSRouteRule(t *testing.T) {
},
{
Path: "api/admin",
Url: "https://www.google.com",
URL: "https://www.google.com",
ReqRole: models.ROLE_ADMIN,
Headers: []plugins.AppPluginRouteHeader{
{Name: "x-header", Content: "my secret {{.SecureJsonData.key}}"},
@@ -52,14 +52,17 @@ func TestDSRouteRule(t *testing.T) {
},
{
Path: "api/anon",
Url: "https://www.google.com",
URL: "https://www.google.com",
Headers: []plugins.AppPluginRouteHeader{
{Name: "x-header", Content: "my secret {{.SecureJsonData.key}}"},
},
},
{
Path: "api/common",
Url: "{{.JsonData.dynamicUrl}}",
URL: "{{.JsonData.dynamicUrl}}",
URLParams: []plugins.AppPluginRouteURLParam{
{Name: "{{.JsonData.queryParam}}", Content: "{{.SecureJsonData.key}}"},
},
Headers: []plugins.AppPluginRouteHeader{
{Name: "x-header", Content: "my secret {{.SecureJsonData.key}}"},
},
@@ -74,6 +77,7 @@ func TestDSRouteRule(t *testing.T) {
JsonData: simplejson.NewFromAny(map[string]interface{}{
"clientId": "asd",
"dynamicUrl": "https://dynamic.grafana.com",
"queryParam": "apiKey",
}),
SecureJsonData: map[string][]byte{
"key": key,
@@ -106,8 +110,8 @@ func TestDSRouteRule(t *testing.T) {
proxy.route = plugin.Routes[3]
ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, proxy.route, proxy.ds)
Convey("should add headers and interpolate the url", func() {
So(req.URL.String(), ShouldEqual, "https://dynamic.grafana.com/some/method")
Convey("should add headers and interpolate the url with query string parameters", func() {
So(req.URL.String(), ShouldEqual, "https://dynamic.grafana.com/some/method?apiKey=123")
So(req.Header.Get("x-header"), ShouldEqual, "my secret 123")
})
})
@@ -142,7 +146,7 @@ func TestDSRouteRule(t *testing.T) {
Routes: []*plugins.AppPluginRoute{
{
Path: "pathwithtoken1",
Url: "https://api.nr1.io/some/path",
URL: "https://api.nr1.io/some/path",
TokenAuth: &plugins.JwtTokenAuth{
Url: "https://login.server.com/{{.JsonData.tenantId}}/oauth2/token",
Params: map[string]string{
@@ -155,7 +159,7 @@ func TestDSRouteRule(t *testing.T) {
},
{
Path: "pathwithtoken2",
Url: "https://api.nr2.io/some/path",
URL: "https://api.nr2.io/some/path",
TokenAuth: &plugins.JwtTokenAuth{
Url: "https://login.server.com/{{.JsonData.tenantId}}/oauth2/token",
Params: map[string]string{
+3 -3
View File
@@ -48,7 +48,7 @@ func updateURL(route *plugins.AppPluginRoute, orgId int64, appID string) (string
JsonData: query.Result.JsonData,
SecureJsonData: query.Result.SecureJsonData.Decrypt(),
}
interpolated, err := InterpolateString(route.Url, data)
interpolated, err := InterpolateString(route.URL, data)
if err != nil {
return "", err
}
@@ -57,7 +57,7 @@ func updateURL(route *plugins.AppPluginRoute, orgId int64, appID string) (string
// NewApiPluginProxy create a plugin proxy
func NewApiPluginProxy(ctx *models.ReqContext, proxyPath string, route *plugins.AppPluginRoute, appID string, cfg *setting.Cfg) *httputil.ReverseProxy {
targetURL, _ := url.Parse(route.Url)
targetURL, _ := url.Parse(route.URL)
director := func(req *http.Request) {
@@ -98,7 +98,7 @@ func NewApiPluginProxy(ctx *models.ReqContext, proxyPath string, route *plugins.
}
}
if len(route.Url) > 0 {
if len(route.URL) > 0 {
interpolatedURL, err := updateURL(route, ctx.OrgId, appID)
if err != nil {
ctx.JsonApiErr(500, "Could not interpolate plugin route url", err)
+4 -4
View File
@@ -95,7 +95,7 @@ func TestPluginProxy(t *testing.T) {
Convey("When getting templated url", t, func() {
route := &plugins.AppPluginRoute{
Url: "{{.JsonData.dynamicUrl}}",
URL: "{{.JsonData.dynamicUrl}}",
Method: "GET",
}
@@ -126,7 +126,7 @@ func TestPluginProxy(t *testing.T) {
So(req.URL.String(), ShouldEqual, "https://dynamic.grafana.com")
})
Convey("Route url should not be modified", func() {
So(route.Url, ShouldEqual, "{{.JsonData.dynamicUrl}}")
So(route.URL, ShouldEqual, "{{.JsonData.dynamicUrl}}")
})
})
@@ -138,13 +138,13 @@ func getPluginProxiedRequest(ctx *models.ReqContext, cfg *setting.Cfg, route *pl
if route == nil {
route = &plugins.AppPluginRoute{
Path: "api/v4/",
Url: "https://www.google.com",
URL: "https://www.google.com",
ReqRole: models.ROLE_EDITOR,
}
}
proxy := NewApiPluginProxy(ctx, "", route, "", cfg)
req, err := http.NewRequest(http.MethodGet, route.Url, nil)
req, err := http.NewRequest(http.MethodGet, route.URL, nil)
So(err, ShouldBeNil)
proxy.Director(req)
return req