From c8189e4808f83019486edb9637e840cb0fe1e6aa Mon Sep 17 00:00:00 2001 From: Sofia Papagiannaki <1632407+papagian@users.noreply.github.com> Date: Thu, 14 Apr 2022 13:28:13 +0300 Subject: [PATCH] API: enable proxying datasource calls using the datasource UID (#47634) * Introduce additional routes --- pkg/api/api.go | 2 + pkg/api/dataproxy.go | 4 ++ .../datasourceproxy/datasourceproxy.go | 46 ++++++++++++++----- .../datasourceproxy/datasourceproxy_test.go | 12 +++++ 4 files changed, 53 insertions(+), 11 deletions(-) diff --git a/pkg/api/api.go b/pkg/api/api.go index f54e5ee7a94..4836363dfaf 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -322,7 +322,9 @@ func (hs *HTTPServer) registerRoutes() { apiRoute.Get("/frontend/settings/", hs.GetFrontendSettings) 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/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/health", authorize(reqSignedIn, ac.EvalPermission(datasources.ActionQuery)), routing.Wrap(hs.CheckDatasourceHealth)) diff --git a/pkg/api/dataproxy.go b/pkg/api/dataproxy.go index 5a39872d20e..af006072019 100644 --- a/pkg/api/dataproxy.go +++ b/pkg/api/dataproxy.go @@ -5,3 +5,7 @@ import "github.com/grafana/grafana/pkg/models" func (hs *HTTPServer) ProxyDataSourceRequest(c *models.ReqContext) { hs.DataProxy.ProxyDataSourceRequest(c) } + +func (hs *HTTPServer) ProxyDataSourceRequestWithUID(c *models.ReqContext) { + hs.DataProxy.ProxyDatasourceRequestWithUID(c) +} diff --git a/pkg/services/datasourceproxy/datasourceproxy.go b/pkg/services/datasourceproxy/datasourceproxy.go index fc101016b87..75d5f506ea1 100644 --- a/pkg/services/datasourceproxy/datasourceproxy.go +++ b/pkg/services/datasourceproxy/datasourceproxy.go @@ -18,6 +18,7 @@ import ( "github.com/grafana/grafana/pkg/services/oauthtoken" "github.com/grafana/grafana/pkg/services/secrets" "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/web" ) @@ -59,24 +60,47 @@ func (p *DataSourceProxyService) ProxyDataSourceRequest(c *models.ReqContext) { 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) { c.TimeRequest(metrics.MDataSourceProxyReqTimer) ds, err := p.DataSourceCache.GetDatasource(c.Req.Context(), dsID, c.SignedInUser, c.SkipCache) if err != nil { - if errors.Is(err, models.ErrDataSourceAccessDenied) { - c.JsonApiErr(http.StatusForbidden, "Access denied to datasource", err) - 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) + toAPIError(c, err) + } + p.proxyDatasourceRequest(c, ds) +} + +func toAPIError(c *models.ReqContext, err error) { + if errors.Is(err, models.ErrDataSourceAccessDenied) { + c.JsonApiErr(http.StatusForbidden, "Access denied to datasource", err) 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 { c.JsonApiErr(http.StatusForbidden, "Access denied", err) return @@ -103,7 +127,7 @@ func (p *DataSourceProxyService) ProxyDatasourceRequestWithID(c *models.ReqConte 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 { return proxyPathRegexp.ReplaceAllString(originalRawPath, "") diff --git a/pkg/services/datasourceproxy/datasourceproxy_test.go b/pkg/services/datasourceproxy/datasourceproxy_test.go index 4533fbf74d9..6567ba579c5 100644 --- a/pkg/services/datasourceproxy/datasourceproxy_test.go +++ b/pkg/services/datasourceproxy/datasourceproxy_test.go @@ -24,6 +24,18 @@ func TestDataProxy(t *testing.T) { "/api/datasources/proxy/54/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 { t.Run("Given raw path, should extract expected proxy path", func(t *testing.T) {