| 
									
										
										
										
											2016-02-25 21:55:31 +08:00
										 |  |  | package api | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2022-12-02 20:46:55 +08:00
										 |  |  | 	"bytes" | 
					
						
							| 
									
										
										
										
											2021-11-17 19:04:22 +08:00
										 |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2020-04-21 22:16:41 +08:00
										 |  |  | 	"encoding/json" | 
					
						
							| 
									
										
										
										
											2020-03-13 19:31:44 +08:00
										 |  |  | 	"errors" | 
					
						
							| 
									
										
										
										
											2021-11-01 17:53:33 +08:00
										 |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2020-03-18 19:08:20 +08:00
										 |  |  | 	"net/http" | 
					
						
							| 
									
										
										
										
											2022-02-05 01:14:53 +08:00
										 |  |  | 	"path" | 
					
						
							| 
									
										
										
										
											2021-04-21 21:17:23 +08:00
										 |  |  | 	"path/filepath" | 
					
						
							| 
									
										
										
										
											2022-08-23 17:50:50 +08:00
										 |  |  | 	"runtime" | 
					
						
							| 
									
										
										
										
											2016-03-15 22:52:29 +08:00
										 |  |  | 	"sort" | 
					
						
							| 
									
										
										
										
											2021-11-01 17:53:33 +08:00
										 |  |  | 	"strings" | 
					
						
							| 
									
										
										
										
											2016-03-15 22:52:29 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-27 22:08:17 +08:00
										 |  |  | 	"github.com/prometheus/client_golang/prometheus" | 
					
						
							|  |  |  | 	"github.com/prometheus/client_golang/prometheus/promauto" | 
					
						
							| 
									
										
										
										
											2023-01-24 02:56:20 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-06 17:34:36 +08:00
										 |  |  | 	"github.com/grafana/grafana-plugin-sdk-go/backend" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-25 21:55:31 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/api/dtos" | 
					
						
							| 
									
										
										
										
											2021-01-15 21:43:20 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/api/response" | 
					
						
							| 
									
										
										
										
											2016-02-25 21:55:31 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/plugins" | 
					
						
							| 
									
										
										
										
											2024-03-07 18:09:19 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/plugins/pfs" | 
					
						
							| 
									
										
										
										
											2022-08-23 17:50:50 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/plugins/repo" | 
					
						
							| 
									
										
										
										
											2022-09-09 15:44:50 +08:00
										 |  |  | 	ac "github.com/grafana/grafana/pkg/services/accesscontrol" | 
					
						
							| 
									
										
										
										
											2023-01-27 15:50:36 +08:00
										 |  |  | 	contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" | 
					
						
							| 
									
										
										
										
											2022-09-09 15:44:50 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/services/datasources" | 
					
						
							| 
									
										
										
										
											2023-11-24 23:02:44 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/services/featuremgmt" | 
					
						
							| 
									
										
										
										
											2022-09-09 15:44:50 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/services/org" | 
					
						
							| 
									
										
										
										
											2023-03-27 17:15:37 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginaccesscontrol" | 
					
						
							| 
									
										
										
										
											2023-03-08 00:22:30 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings" | 
					
						
							| 
									
										
										
										
											2023-09-11 19:59:24 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore" | 
					
						
							| 
									
										
										
										
											2021-05-13 02:05:16 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/setting" | 
					
						
							| 
									
										
										
										
											2022-12-02 20:46:55 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/util" | 
					
						
							| 
									
										
										
										
											2021-10-11 20:30:59 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/web" | 
					
						
							| 
									
										
										
										
											2016-02-25 21:55:31 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-27 22:08:17 +08:00
										 |  |  | // pluginsCDNFallbackRedirectRequests is a metric counter keeping track of how many
 | 
					
						
							|  |  |  | // requests are received on the plugins CDN backend redirect fallback handler.
 | 
					
						
							|  |  |  | var pluginsCDNFallbackRedirectRequests = promauto.NewCounterVec(prometheus.CounterOpts{ | 
					
						
							|  |  |  | 	Namespace: "grafana", | 
					
						
							|  |  |  | 	Name:      "plugins_cdn_fallback_redirect_requests_total", | 
					
						
							|  |  |  | 	Help:      "Number of requests to the plugins CDN backend redirect fallback handler.", | 
					
						
							|  |  |  | }, []string{"plugin_id", "plugin_version"}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-28 00:44:06 +08:00
										 |  |  | var ErrUnexpectedFileExtension = errors.New("unexpected file extension") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-27 15:50:36 +08:00
										 |  |  | func (hs *HTTPServer) GetPluginList(c *contextmodel.ReqContext) response.Response { | 
					
						
							| 
									
										
										
										
											2016-03-09 01:17:47 +08:00
										 |  |  | 	typeFilter := c.Query("type") | 
					
						
							|  |  |  | 	enabledFilter := c.Query("enabled") | 
					
						
							|  |  |  | 	embeddedFilter := c.Query("embedded") | 
					
						
							| 
									
										
										
										
											2022-09-09 15:44:50 +08:00
										 |  |  | 	// "" => no filter
 | 
					
						
							|  |  |  | 	// "0" => filter out core plugins
 | 
					
						
							|  |  |  | 	// "1" => filter out non-core plugins
 | 
					
						
							| 
									
										
										
										
											2016-04-09 04:42:33 +08:00
										 |  |  | 	coreFilter := c.Query("core") | 
					
						
							| 
									
										
										
										
											2016-03-09 01:17:47 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-09 15:44:50 +08:00
										 |  |  | 	// FIXME: while we don't have permissions for listing plugins we need this complex check:
 | 
					
						
							|  |  |  | 	// When using access control, should be able to list non-core plugins:
 | 
					
						
							|  |  |  | 	//  * anyone that can create a data source
 | 
					
						
							|  |  |  | 	//  * anyone that can install a plugin
 | 
					
						
							| 
									
										
										
										
											2022-05-13 16:30:26 +08:00
										 |  |  | 	// Fallback to only letting admins list non-core plugins
 | 
					
						
							| 
									
										
										
										
											2022-09-09 15:44:50 +08:00
										 |  |  | 	reqOrgAdmin := ac.ReqHasRole(org.RoleAdmin) | 
					
						
							|  |  |  | 	hasAccess := ac.HasAccess(hs.AccessControl, c) | 
					
						
							| 
									
										
										
										
											2023-05-30 21:39:09 +08:00
										 |  |  | 	canListNonCorePlugins := reqOrgAdmin(c) || hasAccess(ac.EvalAny( | 
					
						
							| 
									
										
										
										
											2022-09-09 15:44:50 +08:00
										 |  |  | 		ac.EvalPermission(datasources.ActionCreate), | 
					
						
							| 
									
										
										
										
											2023-03-27 17:15:37 +08:00
										 |  |  | 		ac.EvalPermission(pluginaccesscontrol.ActionInstall), | 
					
						
							| 
									
										
										
										
											2022-09-09 15:44:50 +08:00
										 |  |  | 	)) | 
					
						
							| 
									
										
										
										
											2020-03-16 22:40:46 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-06 17:34:36 +08:00
										 |  |  | 	pluginSettingsMap, err := hs.pluginSettings(c.Req.Context(), c.SignedInUser.GetOrgID()) | 
					
						
							| 
									
										
										
										
											2016-02-25 21:55:31 +08:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2022-09-09 15:44:50 +08:00
										 |  |  | 		return response.Error(http.StatusInternalServerError, "Failed to get list of plugins", err) | 
					
						
							| 
									
										
										
										
											2016-02-25 21:55:31 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-09 15:44:50 +08:00
										 |  |  | 	// Filter plugins
 | 
					
						
							|  |  |  | 	pluginDefinitions := hs.pluginStore.Plugins(c.Req.Context()) | 
					
						
							| 
									
										
										
										
											2023-09-11 19:59:24 +08:00
										 |  |  | 	filteredPluginDefinitions := []pluginstore.Plugin{} | 
					
						
							| 
									
										
										
										
											2022-09-09 15:44:50 +08:00
										 |  |  | 	filteredPluginIDs := map[string]bool{} | 
					
						
							|  |  |  | 	for _, pluginDef := range pluginDefinitions { | 
					
						
							| 
									
										
										
										
											2016-03-09 01:17:47 +08:00
										 |  |  | 		// filter out app sub plugins
 | 
					
						
							| 
									
										
										
										
											2021-11-01 17:53:33 +08:00
										 |  |  | 		if embeddedFilter == "0" && pluginDef.IncludedInAppID != "" { | 
					
						
							| 
									
										
										
										
											2016-03-09 01:17:47 +08:00
										 |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-04-09 04:42:33 +08:00
										 |  |  | 		// filter out core plugins
 | 
					
						
							| 
									
										
										
										
											2021-11-01 17:53:33 +08:00
										 |  |  | 		if (coreFilter == "0" && pluginDef.IsCorePlugin()) || (coreFilter == "1" && !pluginDef.IsCorePlugin()) { | 
					
						
							| 
									
										
										
										
											2016-04-09 04:42:33 +08:00
										 |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-09 15:44:50 +08:00
										 |  |  | 		// FIXME: while we don't have permissions for listing plugins we need this complex check:
 | 
					
						
							|  |  |  | 		// When using access control, should be able to list non-core plugins:
 | 
					
						
							|  |  |  | 		//  * anyone that can create a data source
 | 
					
						
							|  |  |  | 		//  * anyone that can install a plugin
 | 
					
						
							|  |  |  | 		// Should be able to list this installed plugin:
 | 
					
						
							|  |  |  | 		//  * anyone that can edit its settings
 | 
					
						
							| 
									
										
										
										
											2023-05-30 21:39:09 +08:00
										 |  |  | 		if !pluginDef.IsCorePlugin() && !canListNonCorePlugins && !hasAccess(ac.EvalPermission(pluginaccesscontrol.ActionWrite, pluginaccesscontrol.ScopeProvider.GetResourceScope(pluginDef.ID))) { | 
					
						
							| 
									
										
										
										
											2022-09-09 15:44:50 +08:00
										 |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-03-09 01:17:47 +08:00
										 |  |  | 		// filter on type
 | 
					
						
							| 
									
										
										
										
											2021-11-01 17:53:33 +08:00
										 |  |  | 		if typeFilter != "" && typeFilter != string(pluginDef.Type) { | 
					
						
							| 
									
										
										
										
											2016-03-08 18:29:36 +08:00
										 |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-08 18:21:19 +08:00
										 |  |  | 		if pluginDef.State == plugins.ReleaseStateAlpha && !hs.Cfg.PluginsEnableAlpha { | 
					
						
							| 
									
										
										
										
											2018-11-15 18:10:47 +08:00
										 |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-09 15:44:50 +08:00
										 |  |  | 		// filter out built in plugins
 | 
					
						
							|  |  |  | 		if pluginDef.BuiltIn { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// filter out disabled plugins
 | 
					
						
							|  |  |  | 		if pluginSetting, exists := pluginSettingsMap[pluginDef.ID]; exists { | 
					
						
							|  |  |  | 			if enabledFilter == "1" && !pluginSetting.Enabled { | 
					
						
							|  |  |  | 				continue | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		filteredPluginDefinitions = append(filteredPluginDefinitions, pluginDef) | 
					
						
							|  |  |  | 		filteredPluginIDs[pluginDef.ID] = true | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Compute metadata
 | 
					
						
							| 
									
										
										
										
											2023-08-18 18:42:18 +08:00
										 |  |  | 	pluginsMetadata := hs.getMultiAccessControlMetadata(c, pluginaccesscontrol.ScopeProvider.GetResourceScope(""), filteredPluginIDs) | 
					
						
							| 
									
										
										
										
											2022-09-09 15:44:50 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	// Prepare DTO
 | 
					
						
							|  |  |  | 	result := make(dtos.PluginList, 0) | 
					
						
							|  |  |  | 	for _, pluginDef := range filteredPluginDefinitions { | 
					
						
							| 
									
										
										
										
											2016-03-15 22:52:29 +08:00
										 |  |  | 		listItem := dtos.PluginListItem{ | 
					
						
							| 
									
										
										
										
											2023-05-12 18:51:11 +08:00
										 |  |  | 			Id:              pluginDef.ID, | 
					
						
							|  |  |  | 			Name:            pluginDef.Name, | 
					
						
							|  |  |  | 			Type:            string(pluginDef.Type), | 
					
						
							|  |  |  | 			Category:        pluginDef.Category, | 
					
						
							|  |  |  | 			Info:            pluginDef.Info, | 
					
						
							|  |  |  | 			Dependencies:    pluginDef.Dependencies, | 
					
						
							|  |  |  | 			DefaultNavUrl:   path.Join(hs.Cfg.AppSubURL, pluginDef.DefaultNavURL), | 
					
						
							|  |  |  | 			State:           pluginDef.State, | 
					
						
							|  |  |  | 			Signature:       pluginDef.Signature, | 
					
						
							|  |  |  | 			SignatureType:   pluginDef.SignatureType, | 
					
						
							|  |  |  | 			SignatureOrg:    pluginDef.SignatureOrg, | 
					
						
							|  |  |  | 			AccessControl:   pluginsMetadata[pluginDef.ID], | 
					
						
							| 
									
										
										
										
											2023-11-10 18:44:54 +08:00
										 |  |  | 			AngularDetected: pluginDef.Angular.Detected, | 
					
						
							| 
									
										
										
										
											2016-02-25 21:55:31 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-20 23:29:13 +08:00
										 |  |  | 		if hs.Features.IsEnabled(c.Req.Context(), featuremgmt.FlagExternalServiceAccounts) { | 
					
						
							|  |  |  | 			listItem.IAM = pluginDef.IAM | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-31 23:06:16 +08:00
										 |  |  | 		update, exists := hs.pluginsUpdateChecker.HasUpdate(c.Req.Context(), pluginDef.ID) | 
					
						
							|  |  |  | 		if exists { | 
					
						
							|  |  |  | 			listItem.LatestVersion = update | 
					
						
							|  |  |  | 			listItem.HasUpdate = true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-01 17:53:33 +08:00
										 |  |  | 		if pluginSetting, exists := pluginSettingsMap[pluginDef.ID]; exists { | 
					
						
							| 
									
										
										
										
											2016-02-25 21:55:31 +08:00
										 |  |  | 			listItem.Enabled = pluginSetting.Enabled | 
					
						
							|  |  |  | 			listItem.Pinned = pluginSetting.Pinned | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-05-04 01:00:42 +08:00
										 |  |  | 		if listItem.DefaultNavUrl == "" || !listItem.Enabled { | 
					
						
							| 
									
										
										
										
											2021-03-10 19:41:29 +08:00
										 |  |  | 			listItem.DefaultNavUrl = hs.Cfg.AppSubURL + "/plugins/" + listItem.Id + "/" | 
					
						
							| 
									
										
										
										
											2016-05-04 01:00:42 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-02-25 21:55:31 +08:00
										 |  |  | 		result = append(result, listItem) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-03-15 22:52:29 +08:00
										 |  |  | 	sort.Sort(result) | 
					
						
							| 
									
										
										
										
											2022-04-15 20:01:58 +08:00
										 |  |  | 	return response.JSON(http.StatusOK, result) | 
					
						
							| 
									
										
										
										
											2016-02-25 21:55:31 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-27 15:50:36 +08:00
										 |  |  | func (hs *HTTPServer) GetPluginSettingByID(c *contextmodel.ReqContext) response.Response { | 
					
						
							| 
									
										
										
										
											2021-10-11 20:30:59 +08:00
										 |  |  | 	pluginID := web.Params(c.Req)[":pluginId"] | 
					
						
							| 
									
										
										
										
											2016-02-25 21:55:31 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-18 20:29:02 +08:00
										 |  |  | 	perr := hs.pluginErrorResolver.PluginError(c.Req.Context(), pluginID) | 
					
						
							|  |  |  | 	if perr != nil { | 
					
						
							|  |  |  | 		return response.Error(http.StatusInternalServerError, perr.PublicMessage(), perr) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-17 19:04:22 +08:00
										 |  |  | 	plugin, exists := hs.pluginStore.Plugin(c.Req.Context(), pluginID) | 
					
						
							|  |  |  | 	if !exists { | 
					
						
							| 
									
										
										
										
											2022-07-08 19:24:09 +08:00
										 |  |  | 		return response.Error(http.StatusNotFound, "Plugin not found, no installed plugin with that id", nil) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// In a first iteration, we only have one permission for app plugins.
 | 
					
						
							|  |  |  | 	// We will need a different permission to allow users to configure the plugin without needing access to it.
 | 
					
						
							|  |  |  | 	if plugin.IsApp() { | 
					
						
							| 
									
										
										
										
											2022-09-09 15:44:50 +08:00
										 |  |  | 		hasAccess := ac.HasAccess(hs.AccessControl, c) | 
					
						
							| 
									
										
										
										
											2023-05-30 21:39:09 +08:00
										 |  |  | 		if !hasAccess(ac.EvalPermission(pluginaccesscontrol.ActionAppAccess, pluginaccesscontrol.ScopeProvider.GetResourceScope(plugin.ID))) { | 
					
						
							| 
									
										
										
										
											2022-07-08 19:24:09 +08:00
										 |  |  | 			return response.Error(http.StatusForbidden, "Access Denied", nil) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2018-03-22 19:37:35 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2016-03-09 01:17:47 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-03-22 19:37:35 +08:00
										 |  |  | 	dto := &dtos.PluginSetting{ | 
					
						
							| 
									
										
										
										
											2022-09-21 18:20:11 +08:00
										 |  |  | 		Type:             string(plugin.Type), | 
					
						
							|  |  |  | 		Id:               plugin.ID, | 
					
						
							|  |  |  | 		Name:             plugin.Name, | 
					
						
							|  |  |  | 		Info:             plugin.Info, | 
					
						
							|  |  |  | 		Dependencies:     plugin.Dependencies, | 
					
						
							|  |  |  | 		Includes:         plugin.Includes, | 
					
						
							|  |  |  | 		BaseUrl:          plugin.BaseURL, | 
					
						
							|  |  |  | 		Module:           plugin.Module, | 
					
						
							|  |  |  | 		DefaultNavUrl:    path.Join(hs.Cfg.AppSubURL, plugin.DefaultNavURL), | 
					
						
							|  |  |  | 		State:            plugin.State, | 
					
						
							|  |  |  | 		Signature:        plugin.Signature, | 
					
						
							|  |  |  | 		SignatureType:    plugin.SignatureType, | 
					
						
							|  |  |  | 		SignatureOrg:     plugin.SignatureOrg, | 
					
						
							|  |  |  | 		SecureJsonFields: map[string]bool{}, | 
					
						
							| 
									
										
										
										
											2023-11-10 18:44:54 +08:00
										 |  |  | 		AngularDetected:  plugin.Angular.Detected, | 
					
						
							| 
									
										
										
										
											2021-11-17 19:04:22 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if plugin.IsApp() { | 
					
						
							|  |  |  | 		dto.Enabled = plugin.AutoEnabled | 
					
						
							|  |  |  | 		dto.Pinned = plugin.AutoEnabled | 
					
						
							| 
									
										
										
										
											2021-02-25 17:00:21 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-19 03:49:13 +08:00
										 |  |  | 	ps, err := hs.PluginSettings.GetPluginSettingByPluginID(c.Req.Context(), &pluginsettings.GetByPluginIDArgs{ | 
					
						
							|  |  |  | 		PluginID: pluginID, | 
					
						
							| 
									
										
										
										
											2023-10-06 17:34:36 +08:00
										 |  |  | 		OrgID:    c.SignedInUser.GetOrgID(), | 
					
						
							| 
									
										
										
										
											2022-03-19 03:49:13 +08:00
										 |  |  | 	}) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2023-01-24 02:56:20 +08:00
										 |  |  | 		if !errors.Is(err, pluginsettings.ErrPluginSettingNotFound) { | 
					
						
							| 
									
										
										
										
											2022-03-19 03:49:13 +08:00
										 |  |  | 			return response.Error(http.StatusInternalServerError, "Failed to get plugin settings", nil) | 
					
						
							| 
									
										
										
										
											2016-02-25 21:55:31 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2018-03-22 19:37:35 +08:00
										 |  |  | 	} else { | 
					
						
							| 
									
										
										
										
											2022-03-19 03:49:13 +08:00
										 |  |  | 		dto.Enabled = ps.Enabled | 
					
						
							|  |  |  | 		dto.Pinned = ps.Pinned | 
					
						
							|  |  |  | 		dto.JsonData = ps.JSONData | 
					
						
							| 
									
										
										
										
											2022-09-21 18:20:11 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		for k, v := range hs.PluginSettings.DecryptedValues(ps) { | 
					
						
							|  |  |  | 			if len(v) > 0 { | 
					
						
							|  |  |  | 				dto.SecureJsonFields[k] = true | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2016-02-25 21:55:31 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-03-22 19:37:35 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-31 23:06:16 +08:00
										 |  |  | 	update, exists := hs.pluginsUpdateChecker.HasUpdate(c.Req.Context(), plugin.ID) | 
					
						
							|  |  |  | 	if exists { | 
					
						
							|  |  |  | 		dto.LatestVersion = update | 
					
						
							|  |  |  | 		dto.HasUpdate = true | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-15 20:01:58 +08:00
										 |  |  | 	return response.JSON(http.StatusOK, dto) | 
					
						
							| 
									
										
										
										
											2016-02-25 21:55:31 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-27 15:50:36 +08:00
										 |  |  | func (hs *HTTPServer) UpdatePluginSetting(c *contextmodel.ReqContext) response.Response { | 
					
						
							| 
									
										
										
										
											2023-01-24 02:56:20 +08:00
										 |  |  | 	cmd := pluginsettings.UpdatePluginSettingCmd{} | 
					
						
							| 
									
										
										
										
											2021-11-29 17:18:01 +08:00
										 |  |  | 	if err := web.Bind(c.Req, &cmd); err != nil { | 
					
						
							|  |  |  | 		return response.Error(http.StatusBadRequest, "bad request data", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-10-11 20:30:59 +08:00
										 |  |  | 	pluginID := web.Params(c.Req)[":pluginId"] | 
					
						
							| 
									
										
										
										
											2016-02-25 21:55:31 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-17 19:04:22 +08:00
										 |  |  | 	if _, exists := hs.pluginStore.Plugin(c.Req.Context(), pluginID); !exists { | 
					
						
							| 
									
										
										
										
											2024-02-28 00:39:51 +08:00
										 |  |  | 		return response.Error(http.StatusNotFound, "Plugin not installed", nil) | 
					
						
							| 
									
										
										
										
											2016-02-25 21:55:31 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-06 17:34:36 +08:00
										 |  |  | 	cmd.OrgId = c.SignedInUser.GetOrgID() | 
					
						
							| 
									
										
										
										
											2021-03-18 20:53:01 +08:00
										 |  |  | 	cmd.PluginId = pluginID | 
					
						
							| 
									
										
										
										
											2022-03-19 03:49:13 +08:00
										 |  |  | 	if err := hs.PluginSettings.UpdatePluginSetting(c.Req.Context(), &pluginsettings.UpdateArgs{ | 
					
						
							|  |  |  | 		Enabled:                 cmd.Enabled, | 
					
						
							|  |  |  | 		Pinned:                  cmd.Pinned, | 
					
						
							|  |  |  | 		JSONData:                cmd.JsonData, | 
					
						
							|  |  |  | 		SecureJSONData:          cmd.SecureJsonData, | 
					
						
							|  |  |  | 		PluginVersion:           cmd.PluginVersion, | 
					
						
							|  |  |  | 		PluginID:                cmd.PluginId, | 
					
						
							|  |  |  | 		OrgID:                   cmd.OrgId, | 
					
						
							|  |  |  | 		EncryptedSecureJSONData: cmd.EncryptedSecureJsonData, | 
					
						
							|  |  |  | 	}); err != nil { | 
					
						
							| 
									
										
										
										
											2024-02-28 00:39:51 +08:00
										 |  |  | 		return response.Error(http.StatusInternalServerError, "Failed to update plugin setting", err) | 
					
						
							| 
									
										
										
										
											2016-02-25 21:55:31 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-08 19:59:51 +08:00
										 |  |  | 	hs.pluginContextProvider.InvalidateSettingsCache(c.Req.Context(), pluginID) | 
					
						
							| 
									
										
										
										
											2023-02-24 19:13:22 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-15 21:43:20 +08:00
										 |  |  | 	return response.Success("Plugin settings updated") | 
					
						
							| 
									
										
										
										
											2016-02-25 21:55:31 +08:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2016-03-11 16:57:20 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-27 15:50:36 +08:00
										 |  |  | func (hs *HTTPServer) GetPluginMarkdown(c *contextmodel.ReqContext) response.Response { | 
					
						
							| 
									
										
										
										
											2021-10-11 20:30:59 +08:00
										 |  |  | 	pluginID := web.Params(c.Req)[":pluginId"] | 
					
						
							|  |  |  | 	name := web.Params(c.Req)[":name"] | 
					
						
							| 
									
										
										
										
											2016-03-14 02:21:44 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-12 19:47:49 +08:00
										 |  |  | 	p, exists := hs.pluginStore.Plugin(c.Req.Context(), pluginID) | 
					
						
							|  |  |  | 	if !exists { | 
					
						
							|  |  |  | 		return response.Error(http.StatusNotFound, "Plugin not installed", nil) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	content, err := hs.pluginMarkdown(c.Req.Context(), pluginID, p.Info.Version, name) | 
					
						
							| 
									
										
										
										
											2018-03-22 19:37:35 +08:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2021-11-01 17:53:33 +08:00
										 |  |  | 		var notFound plugins.NotFoundError | 
					
						
							| 
									
										
										
										
											2020-11-19 20:34:28 +08:00
										 |  |  | 		if errors.As(err, ¬Found) { | 
					
						
							| 
									
										
										
										
											2023-03-07 23:47:02 +08:00
										 |  |  | 			return response.Error(http.StatusNotFound, notFound.Error(), nil) | 
					
						
							| 
									
										
										
										
											2016-03-14 02:21:44 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-07 23:47:02 +08:00
										 |  |  | 		return response.Error(http.StatusInternalServerError, "Could not get markdown file", err) | 
					
						
							| 
									
										
										
										
											2016-03-14 02:21:44 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2018-03-22 19:37:35 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-19 17:53:14 +08:00
										 |  |  | 	// fallback try readme
 | 
					
						
							|  |  |  | 	if len(content) == 0 { | 
					
						
							| 
									
										
										
										
											2024-02-12 19:47:49 +08:00
										 |  |  | 		content, err = hs.pluginMarkdown(c.Req.Context(), pluginID, p.Info.Version, "readme") | 
					
						
							| 
									
										
										
										
											2018-12-19 17:53:14 +08:00
										 |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2023-03-07 23:47:02 +08:00
										 |  |  | 			if errors.Is(err, plugins.ErrFileNotExist) { | 
					
						
							|  |  |  | 				return response.Error(http.StatusNotFound, plugins.ErrFileNotExist.Error(), nil) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return response.Error(http.StatusNotImplemented, "Could not get markdown file", err) | 
					
						
							| 
									
										
										
										
											2018-12-19 17:53:14 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-15 20:01:58 +08:00
										 |  |  | 	resp := response.Respond(http.StatusOK, content) | 
					
						
							| 
									
										
										
										
											2021-03-18 01:12:28 +08:00
										 |  |  | 	resp.SetHeader("Content-Type", "text/plain; charset=utf-8") | 
					
						
							| 
									
										
										
										
											2018-03-22 19:37:35 +08:00
										 |  |  | 	return resp | 
					
						
							| 
									
										
										
										
											2016-03-14 02:21:44 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-18 19:08:20 +08:00
										 |  |  | // CollectPluginMetrics collect metrics from a plugin.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // /api/plugins/:pluginId/metrics
 | 
					
						
							| 
									
										
										
										
											2023-01-27 15:50:36 +08:00
										 |  |  | func (hs *HTTPServer) CollectPluginMetrics(c *contextmodel.ReqContext) response.Response { | 
					
						
							| 
									
										
										
										
											2021-10-11 20:30:59 +08:00
										 |  |  | 	pluginID := web.Params(c.Req)[":pluginId"] | 
					
						
							| 
									
										
										
										
											2022-02-10 00:36:53 +08:00
										 |  |  | 	resp, err := hs.pluginClient.CollectMetrics(c.Req.Context(), &backend.CollectMetricsRequest{PluginContext: backend.PluginContext{PluginID: pluginID}}) | 
					
						
							| 
									
										
										
										
											2020-03-18 19:08:20 +08:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2020-06-11 22:14:05 +08:00
										 |  |  | 		return translatePluginRequestErrorToAPIError(err) | 
					
						
							| 
									
										
										
										
											2020-03-18 19:08:20 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	headers := make(http.Header) | 
					
						
							|  |  |  | 	headers.Set("Content-Type", "text/plain") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-15 21:43:20 +08:00
										 |  |  | 	return response.CreateNormalResponse(headers, resp.PrometheusMetrics, http.StatusOK) | 
					
						
							| 
									
										
										
										
											2020-03-18 19:08:20 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-08 14:49:05 +08:00
										 |  |  | // getPluginAssets returns public plugin assets (images, JS, etc.)
 | 
					
						
							| 
									
										
										
										
											2021-04-21 21:17:23 +08:00
										 |  |  | //
 | 
					
						
							| 
									
										
										
										
											2023-01-27 22:08:17 +08:00
										 |  |  | // If the plugin has cdn = false in its config (default), it will always attempt to return the asset
 | 
					
						
							|  |  |  | // from the local filesystem.
 | 
					
						
							|  |  |  | //
 | 
					
						
							|  |  |  | // If the plugin has cdn = true and hs.Cfg.PluginsCDNURLTemplate is empty, it will get the file
 | 
					
						
							|  |  |  | // from the local filesystem. If hs.Cfg.PluginsCDNURLTemplate is not empty,
 | 
					
						
							|  |  |  | // this handler returns a redirect to the plugin asset file on the specified CDN.
 | 
					
						
							|  |  |  | //
 | 
					
						
							| 
									
										
										
										
											2021-04-21 21:17:23 +08:00
										 |  |  | // /public/plugins/:pluginId/*
 | 
					
						
							| 
									
										
										
										
											2023-01-27 15:50:36 +08:00
										 |  |  | func (hs *HTTPServer) getPluginAssets(c *contextmodel.ReqContext) { | 
					
						
							| 
									
										
										
										
											2021-10-11 20:30:59 +08:00
										 |  |  | 	pluginID := web.Params(c.Req)[":pluginId"] | 
					
						
							| 
									
										
										
										
											2021-11-17 19:04:22 +08:00
										 |  |  | 	plugin, exists := hs.pluginStore.Plugin(c.Req.Context(), pluginID) | 
					
						
							|  |  |  | 	if !exists { | 
					
						
							| 
									
										
										
										
											2021-07-15 14:56:11 +08:00
										 |  |  | 		c.JsonApiErr(404, "Plugin not found", nil) | 
					
						
							| 
									
										
										
										
											2021-07-14 15:38:49 +08:00
										 |  |  | 		return | 
					
						
							| 
									
										
										
										
											2021-04-21 21:17:23 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-08 01:15:53 +08:00
										 |  |  | 	// prepend slash for cleaning relative paths
 | 
					
						
							| 
									
										
										
										
											2022-12-02 20:46:55 +08:00
										 |  |  | 	requestedFile, err := util.CleanRelativePath(web.Params(c.Req)["*"]) | 
					
						
							| 
									
										
										
										
											2021-12-08 01:15:53 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		// slash is prepended above therefore this is not expected to fail
 | 
					
						
							| 
									
										
										
										
											2022-12-02 20:46:55 +08:00
										 |  |  | 		c.JsonApiErr(500, "Failed to clean relative file path", err) | 
					
						
							| 
									
										
										
										
											2021-12-08 01:15:53 +08:00
										 |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-04-21 21:17:23 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-27 22:08:17 +08:00
										 |  |  | 	if hs.pluginsCDNService.PluginSupported(pluginID) { | 
					
						
							|  |  |  | 		// Send a redirect to the client
 | 
					
						
							|  |  |  | 		hs.redirectCDNPluginAsset(c, plugin, requestedFile) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Send the actual file to the client from local filesystem
 | 
					
						
							|  |  |  | 	hs.serveLocalPluginAsset(c, plugin, requestedFile) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // serveLocalPluginAsset returns the content of a plugin asset file from the local filesystem to the http client.
 | 
					
						
							| 
									
										
										
										
											2023-09-11 19:59:24 +08:00
										 |  |  | func (hs *HTTPServer) serveLocalPluginAsset(c *contextmodel.ReqContext, plugin pluginstore.Plugin, assetPath string) { | 
					
						
							| 
									
										
										
										
											2024-02-12 19:47:49 +08:00
										 |  |  | 	f, err := hs.pluginFileStore.File(c.Req.Context(), plugin.ID, plugin.Info.Version, assetPath) | 
					
						
							| 
									
										
										
										
											2021-04-21 21:17:23 +08:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2022-12-02 20:46:55 +08:00
										 |  |  | 		if errors.Is(err, plugins.ErrFileNotExist) { | 
					
						
							|  |  |  | 			c.JsonApiErr(404, "Plugin file not found", nil) | 
					
						
							| 
									
										
										
										
											2021-07-14 15:38:49 +08:00
										 |  |  | 			return | 
					
						
							| 
									
										
										
										
											2021-04-21 21:17:23 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-07-15 14:56:11 +08:00
										 |  |  | 		c.JsonApiErr(500, "Could not open plugin file", err) | 
					
						
							| 
									
										
										
										
											2021-07-14 15:38:49 +08:00
										 |  |  | 		return | 
					
						
							| 
									
										
										
										
											2021-04-21 21:17:23 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if hs.Cfg.Env == setting.Dev { | 
					
						
							| 
									
										
										
										
											2021-07-14 15:38:49 +08:00
										 |  |  | 		c.Resp.Header().Set("Cache-Control", "max-age=0, must-revalidate, no-cache") | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		c.Resp.Header().Set("Cache-Control", "public, max-age=3600") | 
					
						
							| 
									
										
										
										
											2021-04-21 21:17:23 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-29 18:55:55 +08:00
										 |  |  | 	http.ServeContent(c.Resp, c.Req, assetPath, f.ModTime, bytes.NewReader(f.Content)) | 
					
						
							| 
									
										
										
										
											2023-01-27 22:08:17 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // redirectCDNPluginAsset redirects the http request to specified asset path on the configured plugins CDN.
 | 
					
						
							| 
									
										
										
										
											2023-09-11 19:59:24 +08:00
										 |  |  | func (hs *HTTPServer) redirectCDNPluginAsset(c *contextmodel.ReqContext, plugin pluginstore.Plugin, assetPath string) { | 
					
						
							| 
									
										
										
										
											2023-01-27 22:08:17 +08:00
										 |  |  | 	remoteURL, err := hs.pluginsCDNService.AssetURL(plugin.ID, plugin.Info.Version, assetPath) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		c.JsonApiErr(500, "Failed to get CDN plugin asset remote URL", err) | 
					
						
							|  |  |  | 		return | 
					
						
							| 
									
										
										
										
											2022-12-02 20:46:55 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-01-27 22:08:17 +08:00
										 |  |  | 	hs.log.Warn( | 
					
						
							|  |  |  | 		"plugin cdn redirect hit", | 
					
						
							|  |  |  | 		"pluginID", plugin.ID, | 
					
						
							|  |  |  | 		"pluginVersion", plugin.Info.Version, | 
					
						
							|  |  |  | 		"assetPath", assetPath, | 
					
						
							|  |  |  | 		"remoteURL", remoteURL, | 
					
						
							| 
									
										
										
										
											2023-11-10 18:52:06 +08:00
										 |  |  | 		"referer", c.Req.Referer(), | 
					
						
							|  |  |  | 		"user", c.Login, | 
					
						
							| 
									
										
										
										
											2023-01-27 22:08:17 +08:00
										 |  |  | 	) | 
					
						
							|  |  |  | 	pluginsCDNFallbackRedirectRequests.With(prometheus.Labels{ | 
					
						
							|  |  |  | 		"plugin_id":      plugin.ID, | 
					
						
							|  |  |  | 		"plugin_version": plugin.Info.Version, | 
					
						
							|  |  |  | 	}).Inc() | 
					
						
							|  |  |  | 	http.Redirect(c.Resp, c.Req, remoteURL, http.StatusTemporaryRedirect) | 
					
						
							| 
									
										
										
										
											2021-04-21 21:17:23 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-06 21:37:36 +08:00
										 |  |  | // CheckHealth returns the health of a plugin.
 | 
					
						
							| 
									
										
										
										
											2020-01-31 18:15:50 +08:00
										 |  |  | // /api/plugins/:pluginId/health
 | 
					
						
							| 
									
										
										
										
											2023-01-27 15:50:36 +08:00
										 |  |  | func (hs *HTTPServer) CheckHealth(c *contextmodel.ReqContext) response.Response { | 
					
						
							| 
									
										
										
										
											2021-10-11 20:30:59 +08:00
										 |  |  | 	pluginID := web.Params(c.Req)[":pluginId"] | 
					
						
							| 
									
										
										
										
											2023-10-06 17:34:36 +08:00
										 |  |  | 	pCtx, err := hs.pluginContextProvider.Get(c.Req.Context(), pluginID, c.SignedInUser, c.SignedInUser.GetOrgID()) | 
					
						
							| 
									
										
										
										
											2020-03-13 19:31:44 +08:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2023-09-25 18:10:47 +08:00
										 |  |  | 		return response.ErrOrFallback(http.StatusInternalServerError, "Failed to get plugin settings", err) | 
					
						
							| 
									
										
										
										
											2020-03-13 19:31:44 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-11-01 17:53:33 +08:00
										 |  |  | 	resp, err := hs.pluginClient.CheckHealth(c.Req.Context(), &backend.CheckHealthRequest{ | 
					
						
							|  |  |  | 		PluginContext: pCtx, | 
					
						
							| 
									
										
										
										
											2022-05-31 23:58:06 +08:00
										 |  |  | 		Headers:       map[string]string{}, | 
					
						
							| 
									
										
										
										
											2021-11-01 17:53:33 +08:00
										 |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2020-01-31 18:15:50 +08:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2020-06-11 22:14:05 +08:00
										 |  |  | 		return translatePluginRequestErrorToAPIError(err) | 
					
						
							| 
									
										
										
										
											2020-01-31 18:15:50 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-30 23:46:47 +08:00
										 |  |  | 	payload := map[string]any{ | 
					
						
							| 
									
										
										
										
											2020-04-21 22:16:41 +08:00
										 |  |  | 		"status":  resp.Status.String(), | 
					
						
							|  |  |  | 		"message": resp.Message, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Unmarshal JSONDetails if it's not empty.
 | 
					
						
							|  |  |  | 	if len(resp.JSONDetails) > 0 { | 
					
						
							| 
									
										
										
										
											2023-08-30 23:46:47 +08:00
										 |  |  | 		var jsonDetails map[string]any | 
					
						
							| 
									
										
										
										
											2020-04-21 22:16:41 +08:00
										 |  |  | 		err = json.Unmarshal(resp.JSONDetails, &jsonDetails) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2023-09-11 18:13:13 +08:00
										 |  |  | 			return response.Error(http.StatusInternalServerError, "Failed to unmarshal detailed response from backend plugin", err) | 
					
						
							| 
									
										
										
										
											2020-04-21 22:16:41 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		payload["details"] = jsonDetails | 
					
						
							| 
									
										
										
										
											2020-01-31 18:15:50 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-11 22:14:05 +08:00
										 |  |  | 	if resp.Status != backend.HealthStatusOk { | 
					
						
							| 
									
										
										
										
											2023-09-11 18:13:13 +08:00
										 |  |  | 		return response.JSON(http.StatusBadRequest, payload) | 
					
						
							| 
									
										
										
										
											2020-01-31 18:15:50 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-15 20:01:58 +08:00
										 |  |  | 	return response.JSON(http.StatusOK, payload) | 
					
						
							| 
									
										
										
										
											2020-01-31 18:15:50 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-10 16:32:12 +08:00
										 |  |  | func (hs *HTTPServer) GetPluginErrorsList(c *contextmodel.ReqContext) response.Response { | 
					
						
							|  |  |  | 	return response.JSON(http.StatusOK, hs.pluginErrorResolver.PluginErrors(c.Req.Context())) | 
					
						
							| 
									
										
										
										
											2020-10-23 22:45:43 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-27 15:50:36 +08:00
										 |  |  | func (hs *HTTPServer) InstallPlugin(c *contextmodel.ReqContext) response.Response { | 
					
						
							| 
									
										
										
										
											2021-11-29 17:18:01 +08:00
										 |  |  | 	dto := dtos.InstallPluginCommand{} | 
					
						
							|  |  |  | 	if err := web.Bind(c.Req, &dto); err != nil { | 
					
						
							|  |  |  | 		return response.Error(http.StatusBadRequest, "bad request data", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-10-11 20:30:59 +08:00
										 |  |  | 	pluginID := web.Params(c.Req)[":pluginId"] | 
					
						
							| 
									
										
										
										
											2021-05-13 02:05:16 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-30 17:48:52 +08:00
										 |  |  | 	compatOpts := plugins.NewCompatOpts(hs.Cfg.BuildVersion, runtime.GOOS, runtime.GOARCH) | 
					
						
							|  |  |  | 	err := hs.pluginInstaller.Add(c.Req.Context(), pluginID, dto.Version, compatOpts) | 
					
						
							| 
									
										
										
										
											2021-05-13 02:05:16 +08:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2021-11-01 17:53:33 +08:00
										 |  |  | 		var dupeErr plugins.DuplicateError | 
					
						
							| 
									
										
										
										
											2021-05-13 02:05:16 +08:00
										 |  |  | 		if errors.As(err, &dupeErr) { | 
					
						
							|  |  |  | 			return response.Error(http.StatusConflict, "Plugin already installed", err) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2023-05-30 17:48:52 +08:00
										 |  |  | 		var clientError repo.ErrResponse4xx | 
					
						
							| 
									
										
										
										
											2021-07-13 15:58:46 +08:00
										 |  |  | 		if errors.As(err, &clientError) { | 
					
						
							| 
									
										
										
										
											2023-05-30 17:48:52 +08:00
										 |  |  | 			return response.Error(clientError.StatusCode(), clientError.Message(), err) | 
					
						
							| 
									
										
										
										
											2021-05-13 02:05:16 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 		if errors.Is(err, plugins.ErrInstallCorePlugin) { | 
					
						
							|  |  |  | 			return response.Error(http.StatusForbidden, "Cannot install or change a Core plugin", err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-30 22:49:27 +08:00
										 |  |  | 		return response.ErrOrFallback(http.StatusInternalServerError, "Failed to install plugin", err) | 
					
						
							| 
									
										
										
										
											2021-05-13 02:05:16 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 23:02:44 +08:00
										 |  |  | 	if hs.Features.IsEnabled(c.Req.Context(), featuremgmt.FlagExternalServiceAccounts) { | 
					
						
							|  |  |  | 		// This is a non-blocking function that verifies that the installer has
 | 
					
						
							|  |  |  | 		// the permissions that the plugin requests to have on Grafana.
 | 
					
						
							|  |  |  | 		// If we want to make this blocking, the check will have to happen before or during the installation.
 | 
					
						
							|  |  |  | 		hs.hasPluginRequestedPermissions(c, pluginID) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-13 02:05:16 +08:00
										 |  |  | 	return response.JSON(http.StatusOK, []byte{}) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-01-27 15:50:36 +08:00
										 |  |  | func (hs *HTTPServer) UninstallPlugin(c *contextmodel.ReqContext) response.Response { | 
					
						
							| 
									
										
										
										
											2021-10-11 20:30:59 +08:00
										 |  |  | 	pluginID := web.Params(c.Req)[":pluginId"] | 
					
						
							| 
									
										
										
										
											2024-02-12 19:47:49 +08:00
										 |  |  | 	plugin, exists := hs.pluginStore.Plugin(c.Req.Context(), pluginID) | 
					
						
							|  |  |  | 	if !exists { | 
					
						
							|  |  |  | 		return response.Error(http.StatusNotFound, "Plugin not installed", nil) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-05-13 02:05:16 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-12 19:47:49 +08:00
										 |  |  | 	err := hs.pluginInstaller.Remove(c.Req.Context(), pluginID, plugin.Info.Version) | 
					
						
							| 
									
										
										
										
											2021-05-13 02:05:16 +08:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		if errors.Is(err, plugins.ErrPluginNotInstalled) { | 
					
						
							|  |  |  | 			return response.Error(http.StatusNotFound, "Plugin not installed", err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if errors.Is(err, plugins.ErrUninstallCorePlugin) { | 
					
						
							|  |  |  | 			return response.Error(http.StatusForbidden, "Cannot uninstall a Core plugin", err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return response.Error(http.StatusInternalServerError, "Failed to uninstall plugin", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return response.JSON(http.StatusOK, []byte{}) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-01-15 21:43:20 +08:00
										 |  |  | func translatePluginRequestErrorToAPIError(err error) response.Response { | 
					
						
							| 
									
										
										
										
											2023-09-11 18:13:13 +08:00
										 |  |  | 	return response.ErrOrFallback(http.StatusInternalServerError, "Plugin request failed", err) | 
					
						
							| 
									
										
										
										
											2020-06-11 22:14:05 +08:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2021-11-01 17:53:33 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-12 19:47:49 +08:00
										 |  |  | func (hs *HTTPServer) pluginMarkdown(ctx context.Context, pluginID, pluginVersion, name string) ([]byte, error) { | 
					
						
							| 
									
										
										
										
											2023-03-28 00:44:06 +08:00
										 |  |  | 	file, err := mdFilepath(strings.ToUpper(name)) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return make([]byte, 0), err | 
					
						
							| 
									
										
										
										
											2021-11-01 17:53:33 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-12 19:47:49 +08:00
										 |  |  | 	md, err := hs.pluginFileStore.File(ctx, pluginID, pluginVersion, file) | 
					
						
							| 
									
										
										
										
											2021-11-01 17:53:33 +08:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2023-03-29 18:55:55 +08:00
										 |  |  | 		if errors.Is(err, plugins.ErrPluginNotInstalled) { | 
					
						
							|  |  |  | 			return make([]byte, 0), plugins.NotFoundError{PluginID: pluginID} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-12 19:47:49 +08:00
										 |  |  | 		md, err = hs.pluginFileStore.File(ctx, pluginID, pluginVersion, strings.ToLower(file)) | 
					
						
							| 
									
										
										
										
											2022-12-02 20:46:55 +08:00
										 |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return make([]byte, 0), nil | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-11-01 17:53:33 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-03-29 18:55:55 +08:00
										 |  |  | 	return md.Content, nil | 
					
						
							| 
									
										
										
										
											2021-11-01 17:53:33 +08:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2021-12-10 23:10:04 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-24 23:02:44 +08:00
										 |  |  | // hasPluginRequestedPermissions logs if the plugin installer does not have the permissions that the plugin requests to have on Grafana.
 | 
					
						
							|  |  |  | func (hs *HTTPServer) hasPluginRequestedPermissions(c *contextmodel.ReqContext, pluginID string) { | 
					
						
							|  |  |  | 	plugin, ok := hs.pluginStore.Plugin(c.Req.Context(), pluginID) | 
					
						
							|  |  |  | 	if !ok { | 
					
						
							|  |  |  | 		hs.log.Debug("plugin has not been installed", "pluginID", pluginID) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// No registration => Early return
 | 
					
						
							| 
									
										
										
										
											2023-12-04 20:14:21 +08:00
										 |  |  | 	if plugin.JSONData.IAM == nil || len(plugin.JSONData.IAM.Permissions) == 0 { | 
					
						
							| 
									
										
										
										
											2023-11-24 23:02:44 +08:00
										 |  |  | 		hs.log.Debug("plugin did not request permissions on Grafana", "pluginID", pluginID) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	hs.log.Debug("check installer's permissions, plugin wants to register an external service") | 
					
						
							| 
									
										
										
										
											2023-12-04 20:14:21 +08:00
										 |  |  | 	evaluator := evalAllPermissions(plugin.JSONData.IAM.Permissions) | 
					
						
							| 
									
										
										
										
											2024-04-10 18:42:13 +08:00
										 |  |  | 	hasAccess := ac.HasGlobalAccess(hs.AccessControl, hs.authnService, c) | 
					
						
							| 
									
										
										
										
											2023-11-24 23:02:44 +08:00
										 |  |  | 	if hs.Cfg.RBACSingleOrganization { | 
					
						
							|  |  |  | 		// In a single organization setup, no need for a global check
 | 
					
						
							|  |  |  | 		hasAccess = ac.HasAccess(hs.AccessControl, c) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Log a warning if the user does not have the plugin requested permissions
 | 
					
						
							|  |  |  | 	if !hasAccess(evaluator) { | 
					
						
							|  |  |  | 		hs.log.Warn("Plugin installer has less permission than what the plugin requires.", "Permissions", evaluator.String()) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // evalAllPermissions generates an evaluator with all permissions from the input slice
 | 
					
						
							| 
									
										
										
										
											2024-03-07 18:09:19 +08:00
										 |  |  | func evalAllPermissions(ps []pfs.Permission) ac.Evaluator { | 
					
						
							| 
									
										
										
										
											2023-11-24 23:02:44 +08:00
										 |  |  | 	res := []ac.Evaluator{} | 
					
						
							|  |  |  | 	for _, p := range ps { | 
					
						
							|  |  |  | 		if p.Scope != nil { | 
					
						
							|  |  |  | 			res = append(res, ac.EvalPermission(p.Action, *p.Scope)) | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		res = append(res, ac.EvalPermission(p.Action)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return ac.EvalAll(res...) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-28 00:44:06 +08:00
										 |  |  | func mdFilepath(mdFilename string) (string, error) { | 
					
						
							|  |  |  | 	fileExt := filepath.Ext(mdFilename) | 
					
						
							|  |  |  | 	switch fileExt { | 
					
						
							|  |  |  | 	case "md": | 
					
						
							|  |  |  | 		return util.CleanRelativePath(mdFilename) | 
					
						
							|  |  |  | 	case "": | 
					
						
							|  |  |  | 		return util.CleanRelativePath(fmt.Sprintf("%s.md", mdFilename)) | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		return "", ErrUnexpectedFileExtension | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-12-10 23:10:04 +08:00
										 |  |  | } |