| 
									
										
										
										
											2021-08-09 22:07:54 +08:00
										 |  |  | package api | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2022-02-09 20:44:38 +08:00
										 |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2021-09-08 14:49:05 +08:00
										 |  |  | 	"encoding/json" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2022-07-06 22:22:59 +08:00
										 |  |  | 	"io" | 
					
						
							| 
									
										
										
										
											2022-02-09 20:44:38 +08:00
										 |  |  | 	"net/http" | 
					
						
							|  |  |  | 	"net/http/httptest" | 
					
						
							| 
									
										
										
										
											2021-09-08 14:49:05 +08:00
										 |  |  | 	"os" | 
					
						
							|  |  |  | 	"path/filepath" | 
					
						
							| 
									
										
										
										
											2022-07-06 22:22:59 +08:00
										 |  |  | 	"strings" | 
					
						
							| 
									
										
										
										
											2021-08-09 22:07:54 +08:00
										 |  |  | 	"testing" | 
					
						
							| 
									
										
										
										
											2021-09-08 14:49:05 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/stretchr/testify/require" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-09 20:44:38 +08:00
										 |  |  | 	"github.com/grafana/grafana-plugin-sdk-go/backend" | 
					
						
							| 
									
										
										
										
											2022-09-15 00:19:57 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-09 15:44:50 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/api/dtos" | 
					
						
							| 
									
										
										
										
											2021-09-08 14:49:05 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/infra/log" | 
					
						
							| 
									
										
										
										
											2022-07-06 22:22:59 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/infra/log/logtest" | 
					
						
							| 
									
										
										
										
											2021-09-08 14:49:05 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/models" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/plugins" | 
					
						
							| 
									
										
										
										
											2022-09-09 15:44:50 +08:00
										 |  |  | 	ac "github.com/grafana/grafana/pkg/services/accesscontrol" | 
					
						
							| 
									
										
										
										
											2022-08-10 17:56:48 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/services/org" | 
					
						
							| 
									
										
										
										
											2022-09-09 15:44:50 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/services/pluginsettings" | 
					
						
							| 
									
										
										
										
											2022-07-16 00:06:44 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/services/quota/quotatest" | 
					
						
							| 
									
										
										
										
											2022-09-09 15:44:50 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/services/updatechecker" | 
					
						
							| 
									
										
										
										
											2022-08-10 17:56:48 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/services/user" | 
					
						
							| 
									
										
										
										
											2021-09-08 14:49:05 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/setting" | 
					
						
							| 
									
										
										
										
											2022-07-06 22:22:59 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/web/webtest" | 
					
						
							| 
									
										
										
										
											2021-08-09 22:07:54 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-06 22:22:59 +08:00
										 |  |  | func Test_PluginsInstallAndUninstall(t *testing.T) { | 
					
						
							|  |  |  | 	type tc struct { | 
					
						
							|  |  |  | 		pluginAdminEnabled               bool | 
					
						
							|  |  |  | 		pluginAdminExternalManageEnabled bool | 
					
						
							|  |  |  | 		expectedHTTPStatus               int | 
					
						
							|  |  |  | 		expectedHTTPBody                 string | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	tcs := []tc{ | 
					
						
							|  |  |  | 		{pluginAdminEnabled: true, pluginAdminExternalManageEnabled: true, expectedHTTPStatus: 404, expectedHTTPBody: "404 page not found\n"}, | 
					
						
							|  |  |  | 		{pluginAdminEnabled: true, pluginAdminExternalManageEnabled: false, expectedHTTPStatus: 200, expectedHTTPBody: ""}, | 
					
						
							|  |  |  | 		{pluginAdminEnabled: false, pluginAdminExternalManageEnabled: true, expectedHTTPStatus: 404, expectedHTTPBody: "404 page not found\n"}, | 
					
						
							|  |  |  | 		{pluginAdminEnabled: false, pluginAdminExternalManageEnabled: false, expectedHTTPStatus: 404, expectedHTTPBody: "404 page not found\n"}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	testName := func(action string, testCase tc) string { | 
					
						
							|  |  |  | 		return fmt.Sprintf("%s request returns %d when adminEnabled: %t and externalEnabled: %t", | 
					
						
							|  |  |  | 			action, testCase.expectedHTTPStatus, testCase.pluginAdminEnabled, testCase.pluginAdminExternalManageEnabled) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-23 20:27:01 +08:00
										 |  |  | 	inst := NewFakePluginInstaller() | 
					
						
							| 
									
										
										
										
											2022-07-06 22:22:59 +08:00
										 |  |  | 	for _, tc := range tcs { | 
					
						
							|  |  |  | 		srv := SetupAPITestServer(t, func(hs *HTTPServer) { | 
					
						
							|  |  |  | 			hs.Cfg = &setting.Cfg{ | 
					
						
							|  |  |  | 				PluginAdminEnabled:               tc.pluginAdminEnabled, | 
					
						
							|  |  |  | 				PluginAdminExternalManageEnabled: tc.pluginAdminExternalManageEnabled, | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2022-09-23 20:27:01 +08:00
										 |  |  | 			hs.pluginInstaller = inst | 
					
						
							| 
									
										
										
										
											2022-11-15 03:08:10 +08:00
										 |  |  | 			hs.QuotaService = quotatest.New(false, nil) | 
					
						
							| 
									
										
										
										
											2022-07-06 22:22:59 +08:00
										 |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t.Run(testName("Install", tc), func(t *testing.T) { | 
					
						
							|  |  |  | 			req := srv.NewPostRequest("/api/plugins/test/install", strings.NewReader("{ \"version\": \"1.0.2\" }")) | 
					
						
							| 
									
										
										
										
											2022-08-11 19:28:55 +08:00
										 |  |  | 			webtest.RequestWithSignedInUser(req, &user.SignedInUser{UserID: 1, OrgID: 1, OrgRole: org.RoleEditor, IsGrafanaAdmin: true}) | 
					
						
							| 
									
										
										
										
											2022-07-06 22:22:59 +08:00
										 |  |  | 			resp, err := srv.SendJSON(req) | 
					
						
							|  |  |  | 			require.NoError(t, err) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			body := new(strings.Builder) | 
					
						
							|  |  |  | 			_, err = io.Copy(body, resp.Body) | 
					
						
							|  |  |  | 			require.NoError(t, err) | 
					
						
							|  |  |  | 			require.Equal(t, tc.expectedHTTPBody, body.String()) | 
					
						
							|  |  |  | 			require.NoError(t, resp.Body.Close()) | 
					
						
							|  |  |  | 			require.Equal(t, tc.expectedHTTPStatus, resp.StatusCode) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if tc.expectedHTTPStatus == 200 { | 
					
						
							| 
									
										
										
										
											2022-09-23 20:27:01 +08:00
										 |  |  | 				require.Equal(t, fakePlugin{pluginID: "test", version: "1.0.2"}, inst.plugins["test"]) | 
					
						
							| 
									
										
										
										
											2022-07-06 22:22:59 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t.Run(testName("Uninstall", tc), func(t *testing.T) { | 
					
						
							|  |  |  | 			req := srv.NewPostRequest("/api/plugins/test/uninstall", strings.NewReader("{}")) | 
					
						
							| 
									
										
										
										
											2022-08-11 19:28:55 +08:00
										 |  |  | 			webtest.RequestWithSignedInUser(req, &user.SignedInUser{UserID: 1, OrgID: 1, OrgRole: org.RoleViewer, IsGrafanaAdmin: true}) | 
					
						
							| 
									
										
										
										
											2022-07-06 22:22:59 +08:00
										 |  |  | 			resp, err := srv.SendJSON(req) | 
					
						
							|  |  |  | 			require.NoError(t, err) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			body := new(strings.Builder) | 
					
						
							|  |  |  | 			_, err = io.Copy(body, resp.Body) | 
					
						
							|  |  |  | 			require.NoError(t, err) | 
					
						
							|  |  |  | 			require.Equal(t, tc.expectedHTTPBody, body.String()) | 
					
						
							|  |  |  | 			require.NoError(t, resp.Body.Close()) | 
					
						
							|  |  |  | 			require.Equal(t, tc.expectedHTTPStatus, resp.StatusCode) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			if tc.expectedHTTPStatus == 200 { | 
					
						
							| 
									
										
										
										
											2022-09-23 20:27:01 +08:00
										 |  |  | 				require.Empty(t, inst.plugins) | 
					
						
							| 
									
										
										
										
											2022-07-06 22:22:59 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-09 15:44:50 +08:00
										 |  |  | func Test_PluginsInstallAndUninstall_AccessControl(t *testing.T) { | 
					
						
							|  |  |  | 	canInstall := []ac.Permission{{Action: plugins.ActionInstall}} | 
					
						
							|  |  |  | 	cannotInstall := []ac.Permission{{Action: "plugins:cannotinstall"}} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	type testCase struct { | 
					
						
							|  |  |  | 		expectedCode                     int | 
					
						
							|  |  |  | 		permissions                      []ac.Permission | 
					
						
							|  |  |  | 		pluginAdminEnabled               bool | 
					
						
							|  |  |  | 		pluginAdminExternalManageEnabled bool | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	tcs := []testCase{ | 
					
						
							|  |  |  | 		{expectedCode: http.StatusNotFound, permissions: canInstall, pluginAdminEnabled: true, pluginAdminExternalManageEnabled: true}, | 
					
						
							|  |  |  | 		{expectedCode: http.StatusNotFound, permissions: canInstall, pluginAdminEnabled: false, pluginAdminExternalManageEnabled: true}, | 
					
						
							|  |  |  | 		{expectedCode: http.StatusNotFound, permissions: canInstall, pluginAdminEnabled: false, pluginAdminExternalManageEnabled: false}, | 
					
						
							|  |  |  | 		{expectedCode: http.StatusForbidden, permissions: cannotInstall, pluginAdminEnabled: true, pluginAdminExternalManageEnabled: false}, | 
					
						
							|  |  |  | 		{expectedCode: http.StatusOK, permissions: canInstall, pluginAdminEnabled: true, pluginAdminExternalManageEnabled: false}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	testName := func(action string, tc testCase) string { | 
					
						
							|  |  |  | 		return fmt.Sprintf("%s request returns %d when adminEnabled: %t, externalEnabled: %t, permissions: %q", | 
					
						
							|  |  |  | 			action, tc.expectedCode, tc.pluginAdminEnabled, tc.pluginAdminExternalManageEnabled, tc.permissions) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, tc := range tcs { | 
					
						
							|  |  |  | 		sc := setupHTTPServerWithCfg(t, true, &setting.Cfg{ | 
					
						
							|  |  |  | 			RBACEnabled:                      true, | 
					
						
							|  |  |  | 			PluginAdminEnabled:               tc.pluginAdminEnabled, | 
					
						
							|  |  |  | 			PluginAdminExternalManageEnabled: tc.pluginAdminExternalManageEnabled}) | 
					
						
							|  |  |  | 		setInitCtxSignedInViewer(sc.initCtx) | 
					
						
							|  |  |  | 		setAccessControlPermissions(sc.acmock, tc.permissions, sc.initCtx.OrgID) | 
					
						
							| 
									
										
										
										
											2022-09-23 20:27:01 +08:00
										 |  |  | 		sc.hs.pluginInstaller = NewFakePluginInstaller() | 
					
						
							| 
									
										
										
										
											2022-09-09 15:44:50 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		t.Run(testName("Install", tc), func(t *testing.T) { | 
					
						
							|  |  |  | 			input := strings.NewReader("{ \"version\": \"1.0.2\" }") | 
					
						
							|  |  |  | 			response := callAPI(sc.server, http.MethodPost, "/api/plugins/test/install", input, t) | 
					
						
							| 
									
										
										
										
											2022-12-02 20:46:55 +08:00
										 |  |  | 			require.Equal(t, tc.expectedCode, response.Code) | 
					
						
							| 
									
										
										
										
											2022-09-09 15:44:50 +08:00
										 |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t.Run(testName("Uninstall", tc), func(t *testing.T) { | 
					
						
							|  |  |  | 			input := strings.NewReader("{ }") | 
					
						
							|  |  |  | 			response := callAPI(sc.server, http.MethodPost, "/api/plugins/test/uninstall", input, t) | 
					
						
							| 
									
										
										
										
											2022-12-02 20:46:55 +08:00
										 |  |  | 			require.Equal(t, tc.expectedCode, response.Code) | 
					
						
							| 
									
										
										
										
											2022-09-09 15:44:50 +08:00
										 |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-08 14:49:05 +08:00
										 |  |  | func Test_GetPluginAssets(t *testing.T) { | 
					
						
							|  |  |  | 	pluginID := "test-plugin" | 
					
						
							|  |  |  | 	pluginDir := "." | 
					
						
							| 
									
										
										
										
											2022-08-10 21:37:51 +08:00
										 |  |  | 	tmpFile, err := os.CreateTemp(pluginDir, "") | 
					
						
							| 
									
										
										
										
											2021-09-08 14:49:05 +08:00
										 |  |  | 	require.NoError(t, err) | 
					
						
							| 
									
										
										
										
											2022-08-10 21:37:51 +08:00
										 |  |  | 	tmpFileInParentDir, err := os.CreateTemp("..", "") | 
					
						
							| 
									
										
										
										
											2021-12-08 01:15:53 +08:00
										 |  |  | 	require.NoError(t, err) | 
					
						
							| 
									
										
										
										
											2021-09-08 14:49:05 +08:00
										 |  |  | 	t.Cleanup(func() { | 
					
						
							|  |  |  | 		err := os.RemoveAll(tmpFile.Name()) | 
					
						
							| 
									
										
										
										
											2022-12-02 20:46:55 +08:00
										 |  |  | 		require.NoError(t, err) | 
					
						
							| 
									
										
										
										
											2021-12-08 01:15:53 +08:00
										 |  |  | 		err = os.RemoveAll(tmpFileInParentDir.Name()) | 
					
						
							| 
									
										
										
										
											2022-12-02 20:46:55 +08:00
										 |  |  | 		require.NoError(t, err) | 
					
						
							| 
									
										
										
										
											2021-09-08 14:49:05 +08:00
										 |  |  | 	}) | 
					
						
							|  |  |  | 	expectedBody := "Plugin test" | 
					
						
							|  |  |  | 	_, err = tmpFile.WriteString(expectedBody) | 
					
						
							| 
									
										
										
										
											2022-12-02 20:46:55 +08:00
										 |  |  | 	require.NoError(t, err) | 
					
						
							| 
									
										
										
										
											2021-08-09 22:07:54 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-08 14:49:05 +08:00
										 |  |  | 	requestedFile := filepath.Clean(tmpFile.Name()) | 
					
						
							| 
									
										
										
										
											2021-08-09 22:07:54 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-12-02 20:46:55 +08:00
										 |  |  | 	t.Run("Given a request for an existing plugin file", func(t *testing.T) { | 
					
						
							|  |  |  | 		p := &plugins.Plugin{ | 
					
						
							| 
									
										
										
										
											2021-11-01 17:53:33 +08:00
										 |  |  | 			JSONData: plugins.JSONData{ | 
					
						
							|  |  |  | 				ID: pluginID, | 
					
						
							|  |  |  | 			}, | 
					
						
							| 
									
										
										
										
											2021-09-08 14:49:05 +08:00
										 |  |  | 			PluginDir: pluginDir, | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2022-09-28 14:29:35 +08:00
										 |  |  | 		service := &plugins.FakePluginStore{ | 
					
						
							| 
									
										
										
										
											2022-12-02 20:46:55 +08:00
										 |  |  | 			PluginList: []plugins.PluginDTO{p.ToDTO()}, | 
					
						
							| 
									
										
										
										
											2021-09-08 14:49:05 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		url := fmt.Sprintf("/public/plugins/%s/%s", pluginID, requestedFile) | 
					
						
							| 
									
										
										
										
											2022-12-02 20:46:55 +08:00
										 |  |  | 		pluginAssetScenario(t, "When calling GET on", url, "/public/plugins/:pluginId/*", service, | 
					
						
							| 
									
										
										
										
											2021-09-08 14:49:05 +08:00
										 |  |  | 			func(sc *scenarioContext) { | 
					
						
							|  |  |  | 				callGetPluginAsset(sc) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				require.Equal(t, 200, sc.resp.Code) | 
					
						
							| 
									
										
										
										
											2022-12-02 20:46:55 +08:00
										 |  |  | 				require.Equal(t, expectedBody, sc.resp.Body.String()) | 
					
						
							| 
									
										
										
										
											2021-09-08 14:49:05 +08:00
										 |  |  | 			}) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-12-08 01:15:53 +08:00
										 |  |  | 	t.Run("Given a request for a relative path", func(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2022-12-02 20:46:55 +08:00
										 |  |  | 		p := createPluginDTO(plugins.JSONData{ID: pluginID}, plugins.External, pluginDir) | 
					
						
							| 
									
										
										
										
											2022-09-28 14:29:35 +08:00
										 |  |  | 		service := &plugins.FakePluginStore{ | 
					
						
							|  |  |  | 			PluginList: []plugins.PluginDTO{p}, | 
					
						
							| 
									
										
										
										
											2021-12-08 01:15:53 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		url := fmt.Sprintf("/public/plugins/%s/%s", pluginID, tmpFileInParentDir.Name()) | 
					
						
							| 
									
										
										
										
											2022-12-02 20:46:55 +08:00
										 |  |  | 		pluginAssetScenario(t, "When calling GET on", url, "/public/plugins/:pluginId/*", service, | 
					
						
							| 
									
										
										
										
											2021-12-08 01:15:53 +08:00
										 |  |  | 			func(sc *scenarioContext) { | 
					
						
							|  |  |  | 				callGetPluginAsset(sc) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				require.Equal(t, 404, sc.resp.Code) | 
					
						
							|  |  |  | 			}) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-08 14:49:05 +08:00
										 |  |  | 	t.Run("Given a request for an non-existing plugin file", func(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2022-12-02 20:46:55 +08:00
										 |  |  | 		p := createPluginDTO(plugins.JSONData{ID: pluginID}, plugins.External, pluginDir) | 
					
						
							| 
									
										
										
										
											2022-09-28 14:29:35 +08:00
										 |  |  | 		service := &plugins.FakePluginStore{ | 
					
						
							|  |  |  | 			PluginList: []plugins.PluginDTO{p}, | 
					
						
							| 
									
										
										
										
											2021-09-08 14:49:05 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		requestedFile := "nonExistent" | 
					
						
							|  |  |  | 		url := fmt.Sprintf("/public/plugins/%s/%s", pluginID, requestedFile) | 
					
						
							| 
									
										
										
										
											2022-12-02 20:46:55 +08:00
										 |  |  | 		pluginAssetScenario(t, "When calling GET on", url, "/public/plugins/:pluginId/*", service, | 
					
						
							| 
									
										
										
										
											2021-09-08 14:49:05 +08:00
										 |  |  | 			func(sc *scenarioContext) { | 
					
						
							|  |  |  | 				callGetPluginAsset(sc) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				var respJson map[string]interface{} | 
					
						
							|  |  |  | 				err := json.NewDecoder(sc.resp.Body).Decode(&respJson) | 
					
						
							|  |  |  | 				require.NoError(t, err) | 
					
						
							|  |  |  | 				require.Equal(t, 404, sc.resp.Code) | 
					
						
							| 
									
										
										
										
											2022-12-02 20:46:55 +08:00
										 |  |  | 				require.Equal(t, "Plugin file not found", respJson["message"]) | 
					
						
							| 
									
										
										
										
											2021-09-08 14:49:05 +08:00
										 |  |  | 			}) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("Given a request for an non-existing plugin", func(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2022-09-28 14:29:35 +08:00
										 |  |  | 		service := &plugins.FakePluginStore{ | 
					
						
							|  |  |  | 			PluginList: []plugins.PluginDTO{}, | 
					
						
							| 
									
										
										
										
											2021-09-08 14:49:05 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2022-05-06 23:44:22 +08:00
										 |  |  | 		l := &logtest.Fake{} | 
					
						
							| 
									
										
										
										
											2021-09-08 14:49:05 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		requestedFile := "nonExistent" | 
					
						
							|  |  |  | 		url := fmt.Sprintf("/public/plugins/%s/%s", pluginID, requestedFile) | 
					
						
							| 
									
										
										
										
											2022-12-02 20:46:55 +08:00
										 |  |  | 		pluginAssetScenario(t, "When calling GET on", url, "/public/plugins/:pluginId/*", service, | 
					
						
							| 
									
										
										
										
											2021-09-08 14:49:05 +08:00
										 |  |  | 			func(sc *scenarioContext) { | 
					
						
							|  |  |  | 				callGetPluginAsset(sc) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				var respJson map[string]interface{} | 
					
						
							|  |  |  | 				err := json.NewDecoder(sc.resp.Body).Decode(&respJson) | 
					
						
							|  |  |  | 				require.NoError(t, err) | 
					
						
							| 
									
										
										
										
											2022-12-02 20:46:55 +08:00
										 |  |  | 				require.Equal(t, 404, sc.resp.Code) | 
					
						
							|  |  |  | 				require.Equal(t, "Plugin not found", respJson["message"]) | 
					
						
							|  |  |  | 				require.Zero(t, l.WarnLogs.Calls) | 
					
						
							| 
									
										
										
										
											2021-09-08 14:49:05 +08:00
										 |  |  | 			}) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("Given a request for a core plugin's file", func(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2022-09-28 14:29:35 +08:00
										 |  |  | 		service := &plugins.FakePluginStore{ | 
					
						
							|  |  |  | 			PluginList: []plugins.PluginDTO{ | 
					
						
							|  |  |  | 				{ | 
					
						
							|  |  |  | 					JSONData: plugins.JSONData{ID: pluginID}, | 
					
						
							|  |  |  | 					Class:    plugins.Core, | 
					
						
							| 
									
										
										
										
											2021-09-08 14:49:05 +08:00
										 |  |  | 				}, | 
					
						
							| 
									
										
										
										
											2021-08-09 22:07:54 +08:00
										 |  |  | 			}, | 
					
						
							| 
									
										
										
										
											2021-09-08 14:49:05 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2022-05-06 23:44:22 +08:00
										 |  |  | 		l := &logtest.Fake{} | 
					
						
							| 
									
										
										
										
											2021-09-08 14:49:05 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		url := fmt.Sprintf("/public/plugins/%s/%s", pluginID, requestedFile) | 
					
						
							| 
									
										
										
										
											2022-12-02 20:46:55 +08:00
										 |  |  | 		pluginAssetScenario(t, "When calling GET on", url, "/public/plugins/:pluginId/*", service, | 
					
						
							| 
									
										
										
										
											2021-09-08 14:49:05 +08:00
										 |  |  | 			func(sc *scenarioContext) { | 
					
						
							|  |  |  | 				callGetPluginAsset(sc) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				require.Equal(t, 200, sc.resp.Code) | 
					
						
							| 
									
										
										
										
											2022-12-02 20:46:55 +08:00
										 |  |  | 				require.Equal(t, expectedBody, sc.resp.Body.String()) | 
					
						
							|  |  |  | 				require.Zero(t, l.WarnLogs.Calls) | 
					
						
							| 
									
										
										
										
											2021-09-08 14:49:05 +08:00
										 |  |  | 			}) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-09 20:44:38 +08:00
										 |  |  | func TestMakePluginResourceRequest(t *testing.T) { | 
					
						
							|  |  |  | 	hs := HTTPServer{ | 
					
						
							|  |  |  | 		Cfg:          setting.NewCfg(), | 
					
						
							|  |  |  | 		log:          log.New(), | 
					
						
							| 
									
										
										
										
											2022-09-15 00:19:57 +08:00
										 |  |  | 		pluginClient: &fakePluginClient{}, | 
					
						
							| 
									
										
										
										
											2022-02-09 20:44:38 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	req := httptest.NewRequest(http.MethodGet, "/", nil) | 
					
						
							| 
									
										
										
										
											2022-10-21 19:54:55 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-09 20:44:38 +08:00
										 |  |  | 	resp := httptest.NewRecorder() | 
					
						
							|  |  |  | 	pCtx := backend.PluginContext{} | 
					
						
							|  |  |  | 	err := hs.makePluginResourceRequest(resp, req, pCtx) | 
					
						
							|  |  |  | 	require.NoError(t, err) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for { | 
					
						
							|  |  |  | 		if resp.Flushed { | 
					
						
							|  |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-08 00:25:49 +08:00
										 |  |  | 	require.Equal(t, resp.Header().Get("Content-Type"), "application/json") | 
					
						
							| 
									
										
										
										
											2022-02-09 20:44:38 +08:00
										 |  |  | 	require.Equal(t, "sandbox", resp.Header().Get("Content-Security-Policy")) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-09 22:47:32 +08:00
										 |  |  | func TestMakePluginResourceRequestSetCookieNotPresent(t *testing.T) { | 
					
						
							|  |  |  | 	hs := HTTPServer{ | 
					
						
							|  |  |  | 		Cfg: setting.NewCfg(), | 
					
						
							|  |  |  | 		log: log.New(), | 
					
						
							|  |  |  | 		pluginClient: &fakePluginClient{ | 
					
						
							|  |  |  | 			headers: map[string][]string{"Set-Cookie": {"monster"}}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	req := httptest.NewRequest(http.MethodGet, "/", nil) | 
					
						
							|  |  |  | 	resp := httptest.NewRecorder() | 
					
						
							|  |  |  | 	pCtx := backend.PluginContext{} | 
					
						
							|  |  |  | 	err := hs.makePluginResourceRequest(resp, req, pCtx) | 
					
						
							|  |  |  | 	require.NoError(t, err) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for { | 
					
						
							|  |  |  | 		if resp.Flushed { | 
					
						
							|  |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2022-12-02 20:46:55 +08:00
										 |  |  | 	require.Empty(t, resp.Header().Values("Set-Cookie"), "Set-Cookie header should not be present") | 
					
						
							| 
									
										
										
										
											2022-11-09 22:47:32 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestMakePluginResourceRequestContentTypeUnique(t *testing.T) { | 
					
						
							|  |  |  | 	// Ensures Content-Type is present only once, even if it's present with
 | 
					
						
							|  |  |  | 	// a non-canonical key in the plugin response.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Test various upper/lower case combinations for content-type that may be returned by the plugin.
 | 
					
						
							|  |  |  | 	for _, ctHeader := range []string{"content-type", "Content-Type", "CoNtEnT-TyPe"} { | 
					
						
							|  |  |  | 		t.Run(ctHeader, func(t *testing.T) { | 
					
						
							|  |  |  | 			hs := HTTPServer{ | 
					
						
							|  |  |  | 				Cfg: setting.NewCfg(), | 
					
						
							|  |  |  | 				log: log.New(), | 
					
						
							|  |  |  | 				pluginClient: &fakePluginClient{ | 
					
						
							|  |  |  | 					headers: map[string][]string{ | 
					
						
							|  |  |  | 						// This should be "overwritten" by the HTTP server
 | 
					
						
							|  |  |  | 						ctHeader: {"application/json"}, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 						// Another header that should still be present
 | 
					
						
							|  |  |  | 						"x-another": {"hello"}, | 
					
						
							|  |  |  | 					}, | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			req := httptest.NewRequest(http.MethodGet, "/", nil) | 
					
						
							|  |  |  | 			resp := httptest.NewRecorder() | 
					
						
							|  |  |  | 			pCtx := backend.PluginContext{} | 
					
						
							|  |  |  | 			err := hs.makePluginResourceRequest(resp, req, pCtx) | 
					
						
							|  |  |  | 			require.NoError(t, err) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			for { | 
					
						
							|  |  |  | 				if resp.Flushed { | 
					
						
							|  |  |  | 					break | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2022-12-02 20:46:55 +08:00
										 |  |  | 			require.Len(t, resp.Header().Values("Content-Type"), 1, "should have 1 Content-Type header") | 
					
						
							|  |  |  | 			require.Len(t, resp.Header().Values("x-another"), 1, "should have 1 X-Another header") | 
					
						
							| 
									
										
										
										
											2022-11-09 22:47:32 +08:00
										 |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-08 00:25:49 +08:00
										 |  |  | func TestMakePluginResourceRequestContentTypeEmpty(t *testing.T) { | 
					
						
							|  |  |  | 	pluginClient := &fakePluginClient{ | 
					
						
							|  |  |  | 		statusCode: http.StatusNoContent, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	hs := HTTPServer{ | 
					
						
							|  |  |  | 		Cfg:          setting.NewCfg(), | 
					
						
							|  |  |  | 		log:          log.New(), | 
					
						
							|  |  |  | 		pluginClient: pluginClient, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	req := httptest.NewRequest(http.MethodGet, "/", nil) | 
					
						
							|  |  |  | 	resp := httptest.NewRecorder() | 
					
						
							|  |  |  | 	pCtx := backend.PluginContext{} | 
					
						
							|  |  |  | 	err := hs.makePluginResourceRequest(resp, req, pCtx) | 
					
						
							|  |  |  | 	require.NoError(t, err) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for { | 
					
						
							|  |  |  | 		if resp.Flushed { | 
					
						
							|  |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	require.Zero(t, resp.Header().Get("Content-Type")) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-08 14:49:05 +08:00
										 |  |  | func callGetPluginAsset(sc *scenarioContext) { | 
					
						
							|  |  |  | 	sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-11-01 17:53:33 +08:00
										 |  |  | func pluginAssetScenario(t *testing.T, desc string, url string, urlPattern string, pluginStore plugins.Store, | 
					
						
							| 
									
										
										
										
											2022-12-02 20:46:55 +08:00
										 |  |  | 	fn scenarioFunc) { | 
					
						
							| 
									
										
										
										
											2021-09-08 14:49:05 +08:00
										 |  |  | 	t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) { | 
					
						
							|  |  |  | 		hs := HTTPServer{ | 
					
						
							| 
									
										
										
										
											2021-11-01 17:53:33 +08:00
										 |  |  | 			Cfg:         setting.NewCfg(), | 
					
						
							|  |  |  | 			pluginStore: pluginStore, | 
					
						
							| 
									
										
										
										
											2021-09-08 14:49:05 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		sc := setupScenarioContext(t, url) | 
					
						
							|  |  |  | 		sc.defaultHandler = func(c *models.ReqContext) { | 
					
						
							|  |  |  | 			sc.context = c | 
					
						
							|  |  |  | 			hs.getPluginAssets(c) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		sc.m.Get(urlPattern, sc.defaultHandler) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		fn(sc) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-09 20:44:38 +08:00
										 |  |  | type fakePluginClient struct { | 
					
						
							|  |  |  | 	plugins.Client | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	req *backend.CallResourceRequest | 
					
						
							| 
									
										
										
										
											2022-05-04 00:02:20 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	backend.QueryDataHandlerFunc | 
					
						
							| 
									
										
										
										
											2022-11-08 00:25:49 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	statusCode int | 
					
						
							| 
									
										
										
										
											2022-11-09 22:47:32 +08:00
										 |  |  | 	headers    map[string][]string | 
					
						
							| 
									
										
										
										
											2022-02-09 20:44:38 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-05-04 00:02:20 +08:00
										 |  |  | func (c *fakePluginClient) CallResource(_ context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error { | 
					
						
							| 
									
										
										
										
											2022-02-09 20:44:38 +08:00
										 |  |  | 	c.req = req | 
					
						
							|  |  |  | 	bytes, err := json.Marshal(map[string]interface{}{ | 
					
						
							|  |  |  | 		"message": "hello", | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-08 00:25:49 +08:00
										 |  |  | 	statusCode := http.StatusOK | 
					
						
							|  |  |  | 	if c.statusCode != 0 { | 
					
						
							|  |  |  | 		statusCode = c.statusCode | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-09 20:44:38 +08:00
										 |  |  | 	return sender.Send(&backend.CallResourceResponse{ | 
					
						
							| 
									
										
										
										
											2022-11-08 00:25:49 +08:00
										 |  |  | 		Status:  statusCode, | 
					
						
							| 
									
										
										
										
											2022-11-09 22:47:32 +08:00
										 |  |  | 		Headers: c.headers, | 
					
						
							| 
									
										
										
										
											2022-02-09 20:44:38 +08:00
										 |  |  | 		Body:    bytes, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2022-05-04 00:02:20 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | func (c *fakePluginClient) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { | 
					
						
							|  |  |  | 	if c.QueryDataHandlerFunc != nil { | 
					
						
							|  |  |  | 		return c.QueryDataHandlerFunc.QueryData(ctx, req) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return backend.NewQueryDataResponse(), nil | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2022-09-09 15:44:50 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | func Test_PluginsList_AccessControl(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2022-12-02 20:46:55 +08:00
										 |  |  | 	p1 := &plugins.Plugin{ | 
					
						
							|  |  |  | 		PluginDir:     "/grafana/plugins/test-app/dist", | 
					
						
							|  |  |  | 		Class:         plugins.External, | 
					
						
							|  |  |  | 		DefaultNavURL: "/plugins/test-app/page/test", | 
					
						
							|  |  |  | 		Signature:     plugins.SignatureUnsigned, | 
					
						
							|  |  |  | 		Module:        "plugins/test-app/module", | 
					
						
							|  |  |  | 		BaseURL:       "public/plugins/test-app", | 
					
						
							|  |  |  | 		JSONData: plugins.JSONData{ | 
					
						
							|  |  |  | 			ID:   "test-app", | 
					
						
							|  |  |  | 			Type: plugins.App, | 
					
						
							|  |  |  | 			Name: "test-app", | 
					
						
							|  |  |  | 			Info: plugins.Info{ | 
					
						
							|  |  |  | 				Version: "1.0.0", | 
					
						
							| 
									
										
										
										
											2022-09-09 15:44:50 +08:00
										 |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2022-12-02 20:46:55 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	p2 := &plugins.Plugin{ | 
					
						
							|  |  |  | 		PluginDir: "/grafana/public/app/plugins/datasource/mysql", | 
					
						
							|  |  |  | 		Class:     plugins.Core, | 
					
						
							|  |  |  | 		Pinned:    false, | 
					
						
							|  |  |  | 		Signature: plugins.SignatureInternal, | 
					
						
							|  |  |  | 		Module:    "app/plugins/datasource/mysql/module", | 
					
						
							|  |  |  | 		BaseURL:   "public/app/plugins/datasource/mysql", | 
					
						
							|  |  |  | 		JSONData: plugins.JSONData{ | 
					
						
							|  |  |  | 			ID:   "mysql", | 
					
						
							|  |  |  | 			Type: plugins.DataSource, | 
					
						
							|  |  |  | 			Name: "MySQL", | 
					
						
							|  |  |  | 			Info: plugins.Info{ | 
					
						
							|  |  |  | 				Author:      plugins.InfoLink{Name: "Grafana Labs", URL: "https://grafana.com"}, | 
					
						
							|  |  |  | 				Description: "Data source for MySQL databases", | 
					
						
							| 
									
										
										
										
											2022-09-09 15:44:50 +08:00
										 |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2022-12-02 20:46:55 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	pluginStore := plugins.FakePluginStore{PluginList: []plugins.PluginDTO{p1.ToDTO(), p2.ToDTO()}} | 
					
						
							| 
									
										
										
										
											2022-09-28 14:29:35 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	pluginSettings := pluginsettings.FakePluginSettings{Plugins: map[string]*pluginsettings.DTO{ | 
					
						
							| 
									
										
										
										
											2022-09-09 15:44:50 +08:00
										 |  |  | 		"test-app": {ID: 0, OrgID: 1, PluginID: "test-app", PluginVersion: "1.0.0", Enabled: true}, | 
					
						
							|  |  |  | 		"mysql":    {ID: 0, OrgID: 1, PluginID: "mysql", PluginVersion: "", Enabled: true}}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	type testCase struct { | 
					
						
							|  |  |  | 		expectedCode    int | 
					
						
							|  |  |  | 		role            org.RoleType | 
					
						
							|  |  |  | 		isGrafanaAdmin  bool | 
					
						
							|  |  |  | 		expectedPlugins []string | 
					
						
							|  |  |  | 		filters         map[string]string | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	tcs := []testCase{ | 
					
						
							|  |  |  | 		{expectedCode: http.StatusOK, role: org.RoleViewer, expectedPlugins: []string{"mysql"}}, | 
					
						
							|  |  |  | 		{expectedCode: http.StatusOK, role: org.RoleViewer, isGrafanaAdmin: true, expectedPlugins: []string{"mysql", "test-app"}}, | 
					
						
							|  |  |  | 		{expectedCode: http.StatusOK, role: org.RoleAdmin, expectedPlugins: []string{"mysql", "test-app"}}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	testName := func(tc testCase) string { | 
					
						
							|  |  |  | 		return fmt.Sprintf("List request returns %d when role: %s, isGrafanaAdmin: %t, filters: %v", | 
					
						
							|  |  |  | 			tc.expectedCode, tc.role, tc.isGrafanaAdmin, tc.filters) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	testUser := func(role org.RoleType, isGrafanaAdmin bool) user.SignedInUser { | 
					
						
							|  |  |  | 		return user.SignedInUser{ | 
					
						
							|  |  |  | 			UserID:         2, | 
					
						
							|  |  |  | 			OrgID:          2, | 
					
						
							|  |  |  | 			OrgName:        "TestOrg2", | 
					
						
							|  |  |  | 			OrgRole:        role, | 
					
						
							|  |  |  | 			Login:          "testUser", | 
					
						
							|  |  |  | 			Name:           "testUser", | 
					
						
							|  |  |  | 			Email:          "testUser@example.org", | 
					
						
							|  |  |  | 			OrgCount:       1, | 
					
						
							|  |  |  | 			IsGrafanaAdmin: isGrafanaAdmin, | 
					
						
							|  |  |  | 			IsAnonymous:    false, | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, tc := range tcs { | 
					
						
							|  |  |  | 		sc := setupHTTPServer(t, true) | 
					
						
							|  |  |  | 		sc.hs.PluginSettings = &pluginSettings | 
					
						
							|  |  |  | 		sc.hs.pluginStore = pluginStore | 
					
						
							|  |  |  | 		sc.hs.pluginsUpdateChecker = updatechecker.ProvidePluginsService(sc.hs.Cfg, pluginStore) | 
					
						
							|  |  |  | 		setInitCtxSignedInUser(sc.initCtx, testUser(tc.role, tc.isGrafanaAdmin)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t.Run(testName(tc), func(t *testing.T) { | 
					
						
							|  |  |  | 			response := callAPI(sc.server, http.MethodGet, "/api/plugins/", nil, t) | 
					
						
							|  |  |  | 			require.Equal(t, tc.expectedCode, response.Code) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			var res dtos.PluginList | 
					
						
							|  |  |  | 			err := json.NewDecoder(response.Body).Decode(&res) | 
					
						
							|  |  |  | 			require.NoError(t, err) | 
					
						
							|  |  |  | 			require.Len(t, res, len(tc.expectedPlugins)) | 
					
						
							|  |  |  | 			for _, plugin := range res { | 
					
						
							|  |  |  | 				require.Contains(t, tc.expectedPlugins, plugin.Id) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2022-12-02 20:46:55 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | func createPluginDTO(jd plugins.JSONData, class plugins.Class, pluginDir string) plugins.PluginDTO { | 
					
						
							|  |  |  | 	p := &plugins.Plugin{ | 
					
						
							|  |  |  | 		JSONData:  jd, | 
					
						
							|  |  |  | 		Class:     class, | 
					
						
							|  |  |  | 		PluginDir: pluginDir, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return p.ToDTO() | 
					
						
							|  |  |  | } |