API: enable proxying datasource calls using the datasource UID (#47634)

* Introduce additional routes
This commit is contained in:
Sofia Papagiannaki
2022-04-14 13:28:13 +03:00
committed by GitHub
parent 7916f3b638
commit c8189e4808
4 changed files with 53 additions and 11 deletions

View File

@ -322,7 +322,9 @@ func (hs *HTTPServer) registerRoutes() {
apiRoute.Get("/frontend/settings/", hs.GetFrontendSettings) apiRoute.Get("/frontend/settings/", hs.GetFrontendSettings)
apiRoute.Any("/datasources/proxy/:id/*", authorize(reqSignedIn, ac.EvalPermission(datasources.ActionQuery)), hs.ProxyDataSourceRequest) apiRoute.Any("/datasources/proxy/:id/*", authorize(reqSignedIn, ac.EvalPermission(datasources.ActionQuery)), hs.ProxyDataSourceRequest)
apiRoute.Any("/datasources/proxy/uid/:uid/*", authorize(reqSignedIn, ac.EvalPermission(datasources.ActionQuery)), hs.ProxyDataSourceRequestWithUID)
apiRoute.Any("/datasources/proxy/:id", authorize(reqSignedIn, ac.EvalPermission(datasources.ActionQuery)), hs.ProxyDataSourceRequest) apiRoute.Any("/datasources/proxy/:id", authorize(reqSignedIn, ac.EvalPermission(datasources.ActionQuery)), hs.ProxyDataSourceRequest)
apiRoute.Any("/datasources/proxy/uid/:uid", authorize(reqSignedIn, ac.EvalPermission(datasources.ActionQuery)), hs.ProxyDataSourceRequestWithUID)
apiRoute.Any("/datasources/:id/resources", authorize(reqSignedIn, ac.EvalPermission(datasources.ActionQuery)), hs.CallDatasourceResource) apiRoute.Any("/datasources/:id/resources", authorize(reqSignedIn, ac.EvalPermission(datasources.ActionQuery)), hs.CallDatasourceResource)
apiRoute.Any("/datasources/:id/resources/*", authorize(reqSignedIn, ac.EvalPermission(datasources.ActionQuery)), hs.CallDatasourceResource) apiRoute.Any("/datasources/:id/resources/*", authorize(reqSignedIn, ac.EvalPermission(datasources.ActionQuery)), hs.CallDatasourceResource)
apiRoute.Any("/datasources/:id/health", authorize(reqSignedIn, ac.EvalPermission(datasources.ActionQuery)), routing.Wrap(hs.CheckDatasourceHealth)) apiRoute.Any("/datasources/:id/health", authorize(reqSignedIn, ac.EvalPermission(datasources.ActionQuery)), routing.Wrap(hs.CheckDatasourceHealth))

View File

@ -5,3 +5,7 @@ import "github.com/grafana/grafana/pkg/models"
func (hs *HTTPServer) ProxyDataSourceRequest(c *models.ReqContext) { func (hs *HTTPServer) ProxyDataSourceRequest(c *models.ReqContext) {
hs.DataProxy.ProxyDataSourceRequest(c) hs.DataProxy.ProxyDataSourceRequest(c)
} }
func (hs *HTTPServer) ProxyDataSourceRequestWithUID(c *models.ReqContext) {
hs.DataProxy.ProxyDatasourceRequestWithUID(c)
}

View File

@ -18,6 +18,7 @@ import (
"github.com/grafana/grafana/pkg/services/oauthtoken" "github.com/grafana/grafana/pkg/services/oauthtoken"
"github.com/grafana/grafana/pkg/services/secrets" "github.com/grafana/grafana/pkg/services/secrets"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/web" "github.com/grafana/grafana/pkg/web"
) )
@ -59,24 +60,47 @@ func (p *DataSourceProxyService) ProxyDataSourceRequest(c *models.ReqContext) {
p.ProxyDatasourceRequestWithID(c, id) p.ProxyDatasourceRequestWithID(c, id)
} }
func (p *DataSourceProxyService) ProxyDatasourceRequestWithUID(c *models.ReqContext) {
c.TimeRequest(metrics.MDataSourceProxyReqTimer)
dsUID := web.Params(c.Req)[":uid"]
if !util.IsValidShortUID(dsUID) {
c.JsonApiErr(http.StatusBadRequest, "UID is invalid", nil)
return
}
ds, err := p.DataSourceCache.GetDatasourceByUID(c.Req.Context(), dsUID, c.SignedInUser, c.SkipCache)
if err != nil {
toAPIError(c, err)
return
}
p.proxyDatasourceRequest(c, ds)
}
func (p *DataSourceProxyService) ProxyDatasourceRequestWithID(c *models.ReqContext, dsID int64) { func (p *DataSourceProxyService) ProxyDatasourceRequestWithID(c *models.ReqContext, dsID int64) {
c.TimeRequest(metrics.MDataSourceProxyReqTimer) c.TimeRequest(metrics.MDataSourceProxyReqTimer)
ds, err := p.DataSourceCache.GetDatasource(c.Req.Context(), dsID, c.SignedInUser, c.SkipCache) ds, err := p.DataSourceCache.GetDatasource(c.Req.Context(), dsID, c.SignedInUser, c.SkipCache)
if err != nil { if err != nil {
if errors.Is(err, models.ErrDataSourceAccessDenied) { toAPIError(c, err)
c.JsonApiErr(http.StatusForbidden, "Access denied to datasource", err) }
return p.proxyDatasourceRequest(c, ds)
} }
if errors.Is(err, models.ErrDataSourceNotFound) {
c.JsonApiErr(http.StatusNotFound, "Unable to find datasource", err) func toAPIError(c *models.ReqContext, err error) {
return if errors.Is(err, models.ErrDataSourceAccessDenied) {
} c.JsonApiErr(http.StatusForbidden, "Access denied to datasource", err)
c.JsonApiErr(http.StatusInternalServerError, "Unable to load datasource meta data", err)
return return
} }
if errors.Is(err, models.ErrDataSourceNotFound) {
c.JsonApiErr(http.StatusNotFound, "Unable to find datasource", err)
return
}
c.JsonApiErr(http.StatusInternalServerError, "Unable to load datasource meta data", err)
}
err = p.PluginRequestValidator.Validate(ds.Url, c.Req) func (p *DataSourceProxyService) proxyDatasourceRequest(c *models.ReqContext, ds *models.DataSource) {
err := p.PluginRequestValidator.Validate(ds.Url, c.Req)
if err != nil { if err != nil {
c.JsonApiErr(http.StatusForbidden, "Access denied", err) c.JsonApiErr(http.StatusForbidden, "Access denied", err)
return return
@ -103,7 +127,7 @@ func (p *DataSourceProxyService) ProxyDatasourceRequestWithID(c *models.ReqConte
proxy.HandleRequest() proxy.HandleRequest()
} }
var proxyPathRegexp = regexp.MustCompile(`^\/api\/datasources\/proxy\/[\d]+\/?`) var proxyPathRegexp = regexp.MustCompile(`^\/api\/datasources\/proxy\/([\d]+|uid\/[\w]+)\/?`)
func extractProxyPath(originalRawPath string) string { func extractProxyPath(originalRawPath string) string {
return proxyPathRegexp.ReplaceAllString(originalRawPath, "") return proxyPathRegexp.ReplaceAllString(originalRawPath, "")

View File

@ -24,6 +24,18 @@ func TestDataProxy(t *testing.T) {
"/api/datasources/proxy/54/api/services/afsd%2Fafsd/operations", "/api/datasources/proxy/54/api/services/afsd%2Fafsd/operations",
"api/services/afsd%2Fafsd/operations", "api/services/afsd%2Fafsd/operations",
}, },
{
"/api/datasources/proxy/uid/26MI0wZ7k",
"",
},
{
"/api/datasources/proxy/uid/26MI0wZ7k/some/thing",
"some/thing",
},
{
"/api/datasources/proxy/uid/26MI0wZ7k/api/services/afsd%2Fafsd/operations",
"api/services/afsd%2Fafsd/operations",
},
} }
for _, tc := range testCases { for _, tc := range testCases {
t.Run("Given raw path, should extract expected proxy path", func(t *testing.T) { t.Run("Given raw path, should extract expected proxy path", func(t *testing.T) {