|
|
@@ -1,9 +1,13 @@
|
|
|
package pluginproxy
|
|
|
|
|
|
import (
|
|
|
+ "bytes"
|
|
|
+ "fmt"
|
|
|
+ "io/ioutil"
|
|
|
"net/http"
|
|
|
"net/url"
|
|
|
"testing"
|
|
|
+ "time"
|
|
|
|
|
|
macaron "gopkg.in/macaron.v1"
|
|
|
|
|
|
@@ -100,6 +104,109 @@ func TestDSRouteRule(t *testing.T) {
|
|
|
})
|
|
|
})
|
|
|
|
|
|
+ Convey("Plugin with multiple routes for token auth", func() {
|
|
|
+ plugin := &plugins.DataSourcePlugin{
|
|
|
+ Routes: []*plugins.AppPluginRoute{
|
|
|
+ {
|
|
|
+ Path: "pathwithtoken1",
|
|
|
+ Url: "https://api.nr1.io/some/path",
|
|
|
+ TokenAuth: &plugins.JwtTokenAuth{
|
|
|
+ Url: "https://login.server.com/{{.JsonData.tenantId}}/oauth2/token",
|
|
|
+ Params: map[string]string{
|
|
|
+ "grant_type": "client_credentials",
|
|
|
+ "client_id": "{{.JsonData.clientId}}",
|
|
|
+ "client_secret": "{{.SecureJsonData.clientSecret}}",
|
|
|
+ "resource": "https://api.nr1.io",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ Path: "pathwithtoken2",
|
|
|
+ Url: "https://api.nr2.io/some/path",
|
|
|
+ TokenAuth: &plugins.JwtTokenAuth{
|
|
|
+ Url: "https://login.server.com/{{.JsonData.tenantId}}/oauth2/token",
|
|
|
+ Params: map[string]string{
|
|
|
+ "grant_type": "client_credentials",
|
|
|
+ "client_id": "{{.JsonData.clientId}}",
|
|
|
+ "client_secret": "{{.SecureJsonData.clientSecret}}",
|
|
|
+ "resource": "https://api.nr2.io",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ setting.SecretKey = "password"
|
|
|
+ key, _ := util.Encrypt([]byte("123"), "password")
|
|
|
+
|
|
|
+ ds := &m.DataSource{
|
|
|
+ JsonData: simplejson.NewFromAny(map[string]interface{}{
|
|
|
+ "clientId": "asd",
|
|
|
+ "tenantId": "mytenantId",
|
|
|
+ }),
|
|
|
+ SecureJsonData: map[string][]byte{
|
|
|
+ "clientSecret": key,
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ req, _ := http.NewRequest("GET", "http://localhost/asd", nil)
|
|
|
+ ctx := &m.ReqContext{
|
|
|
+ Context: &macaron.Context{
|
|
|
+ Req: macaron.Request{Request: req},
|
|
|
+ },
|
|
|
+ SignedInUser: &m.SignedInUser{OrgRole: m.ROLE_EDITOR},
|
|
|
+ }
|
|
|
+
|
|
|
+ Convey("When creating and caching access tokens", func() {
|
|
|
+ var authorizationHeaderCall1 string
|
|
|
+ var authorizationHeaderCall2 string
|
|
|
+
|
|
|
+ Convey("first call should add authorization header with access token", func() {
|
|
|
+ json, err := ioutil.ReadFile("./test-data/access-token-1.json")
|
|
|
+ So(err, ShouldBeNil)
|
|
|
+
|
|
|
+ proxy1 := NewDataSourceProxyWithMock(ds, plugin, ctx, "pathwithtoken1", json)
|
|
|
+ proxy1.route = plugin.Routes[0]
|
|
|
+ proxy1.applyRoute(req)
|
|
|
+
|
|
|
+ authorizationHeaderCall1 = req.Header.Get("Authorization")
|
|
|
+ So(req.URL.String(), ShouldEqual, "https://api.nr1.io/some/path")
|
|
|
+ So(authorizationHeaderCall1, ShouldStartWith, "Bearer eyJ0e")
|
|
|
+
|
|
|
+ Convey("second call to another route should add a different access token", func() {
|
|
|
+ json2, err := ioutil.ReadFile("./test-data/access-token-2.json")
|
|
|
+ So(err, ShouldBeNil)
|
|
|
+
|
|
|
+ req, _ := http.NewRequest("GET", "http://localhost/asd", nil)
|
|
|
+ proxy2 := NewDataSourceProxyWithMock(ds, plugin, ctx, "pathwithtoken2", json2)
|
|
|
+ proxy2.route = plugin.Routes[1]
|
|
|
+ proxy2.applyRoute(req)
|
|
|
+
|
|
|
+ authorizationHeaderCall2 = req.Header.Get("Authorization")
|
|
|
+
|
|
|
+ So(req.URL.String(), ShouldEqual, "https://api.nr2.io/some/path")
|
|
|
+ So(authorizationHeaderCall1, ShouldStartWith, "Bearer eyJ0e")
|
|
|
+ So(authorizationHeaderCall2, ShouldStartWith, "Bearer eyJ0e")
|
|
|
+ So(authorizationHeaderCall2, ShouldNotEqual, authorizationHeaderCall1)
|
|
|
+
|
|
|
+ Convey("third call to first route should add cached access token", func() {
|
|
|
+ req, _ := http.NewRequest("GET", "http://localhost/asd", nil)
|
|
|
+
|
|
|
+ proxy3 := NewDataSourceProxyWithMock(ds, plugin, ctx, "pathwithtoken1", []byte{})
|
|
|
+ proxy3.route = plugin.Routes[0]
|
|
|
+ proxy3.applyRoute(req)
|
|
|
+
|
|
|
+ authorizationHeaderCall3 := req.Header.Get("Authorization")
|
|
|
+ So(req.URL.String(), ShouldEqual, "https://api.nr1.io/some/path")
|
|
|
+ So(authorizationHeaderCall1, ShouldStartWith, "Bearer eyJ0e")
|
|
|
+ So(authorizationHeaderCall3, ShouldStartWith, "Bearer eyJ0e")
|
|
|
+ So(authorizationHeaderCall3, ShouldEqual, authorizationHeaderCall1)
|
|
|
+ })
|
|
|
+ })
|
|
|
+ })
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
Convey("When proxying graphite", func() {
|
|
|
plugin := &plugins.DataSourcePlugin{}
|
|
|
ds := &m.DataSource{Url: "htttp://graphite:8080", Type: m.DS_GRAPHITE}
|
|
|
@@ -214,3 +321,36 @@ func TestDSRouteRule(t *testing.T) {
|
|
|
|
|
|
})
|
|
|
}
|
|
|
+
|
|
|
+type HttpClientStub struct {
|
|
|
+ fakeBody []byte
|
|
|
+}
|
|
|
+
|
|
|
+func (c *HttpClientStub) Do(req *http.Request) (*http.Response, error) {
|
|
|
+ bodyJSON, _ := simplejson.NewJson(c.fakeBody)
|
|
|
+ _, passedTokenCacheTest := bodyJSON.CheckGet("expires_on")
|
|
|
+ So(passedTokenCacheTest, ShouldBeTrue)
|
|
|
+
|
|
|
+ bodyJSON.Set("expires_on", fmt.Sprint(time.Now().Add(time.Second*60).Unix()))
|
|
|
+ body, _ := bodyJSON.MarshalJSON()
|
|
|
+ resp := &http.Response{
|
|
|
+ Body: ioutil.NopCloser(bytes.NewReader(body)),
|
|
|
+ }
|
|
|
+
|
|
|
+ return resp, nil
|
|
|
+}
|
|
|
+
|
|
|
+func NewDataSourceProxyWithMock(ds *m.DataSource, plugin *plugins.DataSourcePlugin, ctx *m.ReqContext, proxyPath string, fakeBody []byte) *DataSourceProxy {
|
|
|
+ targetURL, _ := url.Parse(ds.Url)
|
|
|
+
|
|
|
+ return &DataSourceProxy{
|
|
|
+ ds: ds,
|
|
|
+ plugin: plugin,
|
|
|
+ ctx: ctx,
|
|
|
+ proxyPath: proxyPath,
|
|
|
+ targetUrl: targetURL,
|
|
|
+ httpClient: &HttpClientStub{
|
|
|
+ fakeBody: fakeBody,
|
|
|
+ },
|
|
|
+ }
|
|
|
+}
|