| 
									
										
										
										
											2022-05-06 16:58:02 +08:00
										 |  |  | package api | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"errors" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"io" | 
					
						
							|  |  |  | 	"net/http" | 
					
						
							|  |  |  | 	"net/url" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/grafana/grafana-plugin-sdk-go/backend" | 
					
						
							| 
									
										
										
										
											2022-06-28 00:23:15 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-11 18:13:13 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/api/response" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/middleware/requestmeta" | 
					
						
							| 
									
										
										
										
											2023-09-25 18:10:47 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/plugins" | 
					
						
							| 
									
										
										
										
											2023-04-28 20:02:27 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/plugins/httpresponsesender" | 
					
						
							| 
									
										
										
										
											2023-01-27 15:50:36 +08:00
										 |  |  | 	contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" | 
					
						
							| 
									
										
										
										
											2022-06-28 00:23:15 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/services/datasources" | 
					
						
							| 
									
										
										
										
											2022-05-06 16:58:02 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/util/proxyutil" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/web" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // CallResource passes a resource call from a plugin to the backend plugin.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // /api/plugins/:pluginId/resources/*
 | 
					
						
							| 
									
										
										
										
											2023-01-27 15:50:36 +08:00
										 |  |  | func (hs *HTTPServer) CallResource(c *contextmodel.ReqContext) { | 
					
						
							| 
									
										
										
										
											2022-05-06 16:58:02 +08:00
										 |  |  | 	hs.callPluginResource(c, web.Params(c.Req)[":pluginId"]) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-27 15:50:36 +08:00
										 |  |  | func (hs *HTTPServer) callPluginResource(c *contextmodel.ReqContext, pluginID string) { | 
					
						
							| 
									
										
										
										
											2023-10-06 17:34:36 +08:00
										 |  |  | 	pCtx, err := hs.pluginContextProvider.Get(c.Req.Context(), pluginID, c.SignedInUser, c.SignedInUser.GetOrgID()) | 
					
						
							| 
									
										
										
										
											2022-05-06 16:58:02 +08:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2023-09-25 18:10:47 +08:00
										 |  |  | 		if errors.Is(err, plugins.ErrPluginNotRegistered) { | 
					
						
							| 
									
										
										
										
											2023-06-08 19:59:51 +08:00
										 |  |  | 			c.JsonApiErr(404, "Plugin not found", nil) | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2022-05-06 16:58:02 +08:00
										 |  |  | 		c.JsonApiErr(500, "Failed to get plugin settings", err) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	req, err := hs.pluginResourceRequest(c) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		c.JsonApiErr(http.StatusBadRequest, "Failed for create plugin resource request", err) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if err = hs.makePluginResourceRequest(c.Resp, req, pCtx); err != nil { | 
					
						
							|  |  |  | 		handleCallResourceError(err, c) | 
					
						
							| 
									
										
										
										
											2023-09-11 18:13:13 +08:00
										 |  |  | 		return | 
					
						
							| 
									
										
										
										
											2022-05-06 16:58:02 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-09-11 18:13:13 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	requestmeta.WithStatusSource(c.Req.Context(), c.Resp.Status()) | 
					
						
							| 
									
										
										
										
											2022-05-06 16:58:02 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-27 15:50:36 +08:00
										 |  |  | func (hs *HTTPServer) callPluginResourceWithDataSource(c *contextmodel.ReqContext, pluginID string, ds *datasources.DataSource) { | 
					
						
							| 
									
										
										
										
											2023-06-08 19:59:51 +08:00
										 |  |  | 	pCtx, err := hs.pluginContextProvider.GetWithDataSource(c.Req.Context(), pluginID, c.SignedInUser, ds) | 
					
						
							| 
									
										
										
										
											2022-05-06 16:58:02 +08:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2023-09-25 18:10:47 +08:00
										 |  |  | 		if errors.Is(err, plugins.ErrPluginNotRegistered) { | 
					
						
							| 
									
										
										
										
											2023-06-08 19:59:51 +08:00
										 |  |  | 			c.JsonApiErr(404, "Plugin not found", nil) | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2022-05-06 16:58:02 +08:00
										 |  |  | 		c.JsonApiErr(500, "Failed to get plugin settings", err) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var dsURL string | 
					
						
							|  |  |  | 	if pCtx.DataSourceInstanceSettings != nil { | 
					
						
							|  |  |  | 		dsURL = pCtx.DataSourceInstanceSettings.URL | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	err = hs.PluginRequestValidator.Validate(dsURL, c.Req) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		c.JsonApiErr(http.StatusForbidden, "Access denied", err) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	req, err := hs.pluginResourceRequest(c) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		c.JsonApiErr(http.StatusBadRequest, "Failed for create plugin resource request", err) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if err = hs.makePluginResourceRequest(c.Resp, req, pCtx); err != nil { | 
					
						
							|  |  |  | 		handleCallResourceError(err, c) | 
					
						
							| 
									
										
										
										
											2023-09-11 18:13:13 +08:00
										 |  |  | 		return | 
					
						
							| 
									
										
										
										
											2022-05-06 16:58:02 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-09-11 18:13:13 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	requestmeta.WithStatusSource(c.Req.Context(), c.Resp.Status()) | 
					
						
							| 
									
										
										
										
											2022-05-06 16:58:02 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-27 15:50:36 +08:00
										 |  |  | func (hs *HTTPServer) pluginResourceRequest(c *contextmodel.ReqContext) (*http.Request, error) { | 
					
						
							| 
									
										
										
										
											2022-05-06 16:58:02 +08:00
										 |  |  | 	clonedReq := c.Req.Clone(c.Req.Context()) | 
					
						
							|  |  |  | 	rawURL := web.Params(c.Req)["*"] | 
					
						
							| 
									
										
										
										
											2024-01-29 17:31:49 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	clonedReq.URL = &url.URL{ | 
					
						
							|  |  |  | 		Path:     rawURL, | 
					
						
							|  |  |  | 		RawQuery: clonedReq.URL.RawQuery, | 
					
						
							| 
									
										
										
										
											2022-05-06 16:58:02 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return clonedReq, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (hs *HTTPServer) makePluginResourceRequest(w http.ResponseWriter, req *http.Request, pCtx backend.PluginContext) error { | 
					
						
							|  |  |  | 	proxyutil.PrepareProxyRequest(req) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-10 21:37:51 +08:00
										 |  |  | 	body, err := io.ReadAll(req.Body) | 
					
						
							| 
									
										
										
										
											2022-05-06 16:58:02 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return fmt.Errorf("failed to read request body: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	crReq := &backend.CallResourceRequest{ | 
					
						
							|  |  |  | 		PluginContext: pCtx, | 
					
						
							|  |  |  | 		Path:          req.URL.Path, | 
					
						
							|  |  |  | 		Method:        req.Method, | 
					
						
							|  |  |  | 		URL:           req.URL.String(), | 
					
						
							|  |  |  | 		Headers:       req.Header, | 
					
						
							|  |  |  | 		Body:          body, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-28 20:02:27 +08:00
										 |  |  | 	httpSender := httpresponsesender.New(w) | 
					
						
							|  |  |  | 	return hs.pluginClient.CallResource(req.Context(), crReq, httpSender) | 
					
						
							| 
									
										
										
										
											2022-05-06 16:58:02 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-27 15:50:36 +08:00
										 |  |  | func handleCallResourceError(err error, reqCtx *contextmodel.ReqContext) { | 
					
						
							| 
									
										
										
										
											2023-09-11 18:13:13 +08:00
										 |  |  | 	resp := response.ErrOrFallback(http.StatusInternalServerError, "Failed to call resource", err) | 
					
						
							|  |  |  | 	resp.WriteTo(reqCtx) | 
					
						
							| 
									
										
										
										
											2022-05-06 16:58:02 +08:00
										 |  |  | } |