mirror of
https://github.com/grafana/grafana.git
synced 2024-11-22 00:47:38 -06:00
Plugins: Support set body content in plugin routes (#32551)
Adds support for overriding the body and length in plugin routes.
This commit is contained in:
parent
027e886997
commit
aad43869c3
@ -131,6 +131,23 @@ To add URL parameters to proxied requests, use the `urlParams` property.
|
||||
]
|
||||
```
|
||||
|
||||
### Set body content
|
||||
|
||||
To set the body content and length of proxied requests, use the `body` property.
|
||||
|
||||
```json
|
||||
"routes": [
|
||||
{
|
||||
"path": "example",
|
||||
"url": "http://api.example.com",
|
||||
"body": {
|
||||
"username": "{{ .JsonData.username }}",
|
||||
"password": "{{ .SecureJsonData.password }}"
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### Enable token authentication
|
||||
|
||||
To enable token-based authentication for proxied requests, use the `tokenAuth` property.
|
||||
|
@ -174,6 +174,7 @@ For data source plugins. Proxy routes used for plugin authentication and adding
|
||||
|
||||
| Property | Type | Required | Description |
|
||||
|----------------|-------------------------|----------|---------------------------------------------------------------------------------------------------------|
|
||||
| `body` | [object](#body) | No | For data source plugins. Route headers set the body content and length to the proxied request. |
|
||||
| `headers` | array | No | For data source plugins. Route headers adds HTTP headers to the proxied request. |
|
||||
| `jwtTokenAuth` | [object](#jwttokenauth) | No | For data source plugins. Token authentication section used with an JWT OAuth API. |
|
||||
| `method` | string | No | For data source plugins. Route method matches the HTTP verb like GET or POST. |
|
||||
@ -183,6 +184,13 @@ For data source plugins. Proxy routes used for plugin authentication and adding
|
||||
| `tokenAuth` | [object](#tokenauth) | No | For data source plugins. Token authentication section used with an OAuth API. |
|
||||
| `url` | string | No | For data source plugins. Route URL is where the request is proxied to. |
|
||||
|
||||
### body
|
||||
|
||||
For data source plugins. Route headers set the body content and length to the proxied request.
|
||||
|
||||
| Property | Type | Required | Description |
|
||||
|----------|------|----------|-------------|
|
||||
|
||||
### jwtTokenAuth
|
||||
|
||||
For data source plugins. Token authentication section used with an JWT OAuth API.
|
||||
|
@ -350,6 +350,10 @@
|
||||
"type": "array",
|
||||
"description": "For data source plugins. Route headers adds HTTP headers to the proxied request."
|
||||
},
|
||||
"body": {
|
||||
"type": "object",
|
||||
"description": "For data source plugins. Route headers set the body content and length to the proxied request."
|
||||
},
|
||||
"tokenAuth": {
|
||||
"type": "object",
|
||||
"description": "For data source plugins. Token authentication section used with an OAuth API.",
|
||||
|
@ -50,6 +50,10 @@ func ApplyRoute(ctx context.Context, req *http.Request, proxyPath string, route
|
||||
logger.Error("Failed to render plugin headers", "error", err)
|
||||
}
|
||||
|
||||
if err := setBodyContent(req, route, data); err != nil {
|
||||
logger.Error("Failed to set plugin route body content", "error", err)
|
||||
}
|
||||
|
||||
tokenProvider := newAccessTokenProvider(ds, route)
|
||||
|
||||
if route.TokenAuth != nil {
|
||||
|
@ -69,6 +69,11 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
|
||||
Path: "api/restricted",
|
||||
ReqRole: models.ROLE_ADMIN,
|
||||
},
|
||||
{
|
||||
Path: "api/body",
|
||||
URL: "http://www.test.com",
|
||||
Body: []byte(`{ "url": "{{.JsonData.dynamicUrl}}", "secret": "{{.SecureJsonData.key}}" }`),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -136,6 +141,18 @@ func TestDataSourceProxy_routeRule(t *testing.T) {
|
||||
assert.Equal(t, "http://localhost/asd", req.URL.String())
|
||||
})
|
||||
|
||||
t.Run("When matching route path and has dynamic body", func(t *testing.T) {
|
||||
ctx, req := setUp()
|
||||
proxy, err := NewDataSourceProxy(ds, plugin, ctx, "api/body", &setting.Cfg{})
|
||||
require.NoError(t, err)
|
||||
proxy.route = plugin.Routes[5]
|
||||
ApplyRoute(proxy.ctx.Req.Context(), req, proxy.proxyPath, proxy.route, proxy.ds)
|
||||
|
||||
content, err := ioutil.ReadAll(req.Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, `{ "url": "https://dynamic.grafana.com", "secret": "123" }`, string(content))
|
||||
})
|
||||
|
||||
t.Run("Validating request", func(t *testing.T) {
|
||||
t.Run("plugin route with valid role", func(t *testing.T) {
|
||||
ctx, _ := setUp()
|
||||
|
@ -70,6 +70,10 @@ func NewApiPluginProxy(ctx *models.ReqContext, proxyPath string, route *plugins.
|
||||
ctx.JsonApiErr(500, "Failed to render plugin headers", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := setBodyContent(req, route, data); err != nil {
|
||||
logger.Error("Failed to set plugin route body content", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
return &httputil.ReverseProxy{Director: director}
|
||||
|
@ -1,10 +1,12 @@
|
||||
package pluginproxy
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/components/securejsondata"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
@ -148,6 +150,38 @@ func TestPluginProxy(t *testing.T) {
|
||||
)
|
||||
assert.Equal(t, "https://example.com", req.URL.String())
|
||||
})
|
||||
|
||||
t.Run("When getting templated body", func(t *testing.T) {
|
||||
route := &plugins.AppPluginRoute{
|
||||
Path: "api/body",
|
||||
URL: "http://www.test.com",
|
||||
Body: []byte(`{ "url": "{{.JsonData.dynamicUrl}}", "secret": "{{.SecureJsonData.key}}" }`),
|
||||
}
|
||||
|
||||
bus.AddHandler("test", func(query *models.GetPluginSettingByIdQuery) error {
|
||||
query.Result = &models.PluginSetting{
|
||||
JsonData: map[string]interface{}{
|
||||
"dynamicUrl": "https://dynamic.grafana.com",
|
||||
},
|
||||
SecureJsonData: securejsondata.GetEncryptedJsonData(map[string]string{"key": "123"}),
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
req := getPluginProxiedRequest(
|
||||
t,
|
||||
&models.ReqContext{
|
||||
SignedInUser: &models.SignedInUser{
|
||||
Login: "test_user",
|
||||
},
|
||||
},
|
||||
&setting.Cfg{SendUserHeader: true},
|
||||
route,
|
||||
)
|
||||
content, err := ioutil.ReadAll(req.Body)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, `{ "url": "https://dynamic.grafana.com", "secret": "123" }`, string(content))
|
||||
})
|
||||
}
|
||||
|
||||
// getPluginProxiedRequest is a helper for easier setup of tests based on global config and ReqContext.
|
||||
|
@ -3,7 +3,9 @@ package pluginproxy
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
@ -60,6 +62,20 @@ func addQueryString(req *http.Request, route *plugins.AppPluginRoute, data templ
|
||||
return nil
|
||||
}
|
||||
|
||||
func setBodyContent(req *http.Request, route *plugins.AppPluginRoute, data templateData) error {
|
||||
if route.Body != nil {
|
||||
interpolatedBody, err := interpolateString(string(route.Body), data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Body = ioutil.NopCloser(strings.NewReader(interpolatedBody))
|
||||
req.ContentLength = int64(len(interpolatedBody))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Set the X-Grafana-User header if needed (and remove if not)
|
||||
func applyUserHeader(sendUserHeader bool, req *http.Request, user *models.SignedInUser) {
|
||||
req.Header.Del("X-Grafana-User")
|
||||
|
@ -35,6 +35,7 @@ type AppPluginRoute struct {
|
||||
Headers []AppPluginRouteHeader `json:"headers"`
|
||||
TokenAuth *JwtTokenAuth `json:"tokenAuth"`
|
||||
JwtTokenAuth *JwtTokenAuth `json:"jwtTokenAuth"`
|
||||
Body json.RawMessage `json:"body"`
|
||||
}
|
||||
|
||||
// AppPluginRouteHeader describes an HTTP header that is forwarded with
|
||||
|
Loading…
Reference in New Issue
Block a user