dsproxy: allow multiple access tokens per datasource
Changes the cache key for tokens to cache on datasource id + route path + http method instead of just datasource id.
This commit is contained in:
@@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user