| 
									
										
										
										
											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/assert" | 
					
						
							|  |  |  | 	"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) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pm := &fakePluginManager{ | 
					
						
							|  |  |  | 		plugins: make(map[string]fakePlugin), | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	for _, tc := range tcs { | 
					
						
							|  |  |  | 		srv := SetupAPITestServer(t, func(hs *HTTPServer) { | 
					
						
							|  |  |  | 			hs.Cfg = &setting.Cfg{ | 
					
						
							|  |  |  | 				PluginAdminEnabled:               tc.pluginAdminEnabled, | 
					
						
							|  |  |  | 				PluginAdminExternalManageEnabled: tc.pluginAdminExternalManageEnabled, | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			hs.pluginManager = pm | 
					
						
							| 
									
										
										
										
											2022-07-16 00:06:44 +08:00
										 |  |  | 			hs.QuotaService = quotatest.NewQuotaServiceFake() | 
					
						
							| 
									
										
										
										
											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 { | 
					
						
							|  |  |  | 				require.Equal(t, fakePlugin{pluginID: "test", version: "1.0.2"}, pm.plugins["test"]) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		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 { | 
					
						
							|  |  |  | 				require.Empty(t, pm.plugins) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pm := &fakePluginManager{ | 
					
						
							|  |  |  | 		plugins: make(map[string]fakePlugin), | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	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) | 
					
						
							|  |  |  | 		sc.hs.pluginManager = pm | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		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) | 
					
						
							|  |  |  | 			assert.Equal(t, tc.expectedCode, response.Code) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		t.Run(testName("Uninstall", tc), func(t *testing.T) { | 
					
						
							|  |  |  | 			input := strings.NewReader("{ }") | 
					
						
							|  |  |  | 			response := callAPI(sc.server, http.MethodPost, "/api/plugins/test/uninstall", input, t) | 
					
						
							|  |  |  | 			assert.Equal(t, tc.expectedCode, response.Code) | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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()) | 
					
						
							|  |  |  | 		assert.NoError(t, err) | 
					
						
							| 
									
										
										
										
											2021-12-08 01:15:53 +08:00
										 |  |  | 		err = os.RemoveAll(tmpFileInParentDir.Name()) | 
					
						
							|  |  |  | 		assert.NoError(t, err) | 
					
						
							| 
									
										
										
										
											2021-09-08 14:49:05 +08:00
										 |  |  | 	}) | 
					
						
							|  |  |  | 	expectedBody := "Plugin test" | 
					
						
							|  |  |  | 	_, err = tmpFile.WriteString(expectedBody) | 
					
						
							|  |  |  | 	assert.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
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-08 14:49:05 +08:00
										 |  |  | 	t.Run("Given a request for an existing plugin file that is listed as a signature covered file", func(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2021-11-17 19:04:22 +08:00
										 |  |  | 		p := plugins.PluginDTO{ | 
					
						
							| 
									
										
										
										
											2021-11-01 17:53:33 +08:00
										 |  |  | 			JSONData: plugins.JSONData{ | 
					
						
							|  |  |  | 				ID: pluginID, | 
					
						
							|  |  |  | 			}, | 
					
						
							| 
									
										
										
										
											2021-09-08 14:49:05 +08:00
										 |  |  | 			PluginDir: pluginDir, | 
					
						
							|  |  |  | 			SignedFiles: map[string]struct{}{ | 
					
						
							|  |  |  | 				requestedFile: {}, | 
					
						
							| 
									
										
										
										
											2021-08-09 22:07:54 +08:00
										 |  |  | 			}, | 
					
						
							| 
									
										
										
										
											2021-09-08 14:49:05 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-11-17 19:04:22 +08:00
										 |  |  | 		service := &fakePluginStore{ | 
					
						
							|  |  |  | 			plugins: map[string]plugins.PluginDTO{ | 
					
						
							| 
									
										
										
										
											2021-09-08 14:49:05 +08:00
										 |  |  | 				pluginID: p, | 
					
						
							| 
									
										
										
										
											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) | 
					
						
							|  |  |  | 		pluginAssetScenario(t, "When calling GET on", url, "/public/plugins/:pluginId/*", service, l, | 
					
						
							|  |  |  | 			func(sc *scenarioContext) { | 
					
						
							|  |  |  | 				callGetPluginAsset(sc) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				require.Equal(t, 200, sc.resp.Code) | 
					
						
							|  |  |  | 				assert.Equal(t, expectedBody, sc.resp.Body.String()) | 
					
						
							| 
									
										
										
										
											2022-05-06 23:44:22 +08:00
										 |  |  | 				assert.Zero(t, l.WarnLogs.Calls) | 
					
						
							| 
									
										
										
										
											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) { | 
					
						
							|  |  |  | 		p := plugins.PluginDTO{ | 
					
						
							|  |  |  | 			JSONData: plugins.JSONData{ | 
					
						
							|  |  |  | 				ID: pluginID, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			PluginDir: pluginDir, | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		service := &fakePluginStore{ | 
					
						
							|  |  |  | 			plugins: map[string]plugins.PluginDTO{ | 
					
						
							|  |  |  | 				pluginID: p, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2022-05-06 23:44:22 +08:00
										 |  |  | 		l := &logtest.Fake{} | 
					
						
							| 
									
										
										
										
											2021-12-08 01:15:53 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		url := fmt.Sprintf("/public/plugins/%s/%s", pluginID, tmpFileInParentDir.Name()) | 
					
						
							|  |  |  | 		pluginAssetScenario(t, "When calling GET on", url, "/public/plugins/:pluginId/*", service, l, | 
					
						
							|  |  |  | 			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 existing plugin file that is not listed as a signature covered file", func(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2021-11-17 19:04:22 +08:00
										 |  |  | 		p := plugins.PluginDTO{ | 
					
						
							| 
									
										
										
										
											2021-11-01 17:53:33 +08:00
										 |  |  | 			JSONData: plugins.JSONData{ | 
					
						
							|  |  |  | 				ID: pluginID, | 
					
						
							|  |  |  | 			}, | 
					
						
							| 
									
										
										
										
											2021-09-08 14:49:05 +08:00
										 |  |  | 			PluginDir: pluginDir, | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-11-17 19:04:22 +08:00
										 |  |  | 		service := &fakePluginStore{ | 
					
						
							|  |  |  | 			plugins: map[string]plugins.PluginDTO{ | 
					
						
							| 
									
										
										
										
											2021-09-08 14:49:05 +08:00
										 |  |  | 				pluginID: p, | 
					
						
							| 
									
										
										
										
											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) | 
					
						
							|  |  |  | 		pluginAssetScenario(t, "When calling GET on", url, "/public/plugins/:pluginId/*", service, l, | 
					
						
							|  |  |  | 			func(sc *scenarioContext) { | 
					
						
							|  |  |  | 				callGetPluginAsset(sc) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				require.Equal(t, 200, sc.resp.Code) | 
					
						
							|  |  |  | 				assert.Equal(t, expectedBody, sc.resp.Body.String()) | 
					
						
							| 
									
										
										
										
											2022-05-06 23:44:22 +08:00
										 |  |  | 				assert.Zero(t, l.WarnLogs.Calls) | 
					
						
							| 
									
										
										
										
											2021-09-08 14:49:05 +08:00
										 |  |  | 			}) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("Given a request for an non-existing plugin file", func(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2021-11-17 19:04:22 +08:00
										 |  |  | 		p := plugins.PluginDTO{ | 
					
						
							| 
									
										
										
										
											2021-11-01 17:53:33 +08:00
										 |  |  | 			JSONData: plugins.JSONData{ | 
					
						
							|  |  |  | 				ID: pluginID, | 
					
						
							|  |  |  | 			}, | 
					
						
							| 
									
										
										
										
											2021-09-08 14:49:05 +08:00
										 |  |  | 			PluginDir: pluginDir, | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-11-17 19:04:22 +08:00
										 |  |  | 		service := &fakePluginStore{ | 
					
						
							|  |  |  | 			plugins: map[string]plugins.PluginDTO{ | 
					
						
							| 
									
										
										
										
											2021-09-08 14:49:05 +08:00
										 |  |  | 				pluginID: p, | 
					
						
							| 
									
										
										
										
											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
										 |  |  | 
 | 
					
						
							|  |  |  | 		requestedFile := "nonExistent" | 
					
						
							|  |  |  | 		url := fmt.Sprintf("/public/plugins/%s/%s", pluginID, requestedFile) | 
					
						
							|  |  |  | 		pluginAssetScenario(t, "When calling GET on", url, "/public/plugins/:pluginId/*", service, l, | 
					
						
							|  |  |  | 			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) | 
					
						
							|  |  |  | 				assert.Equal(t, "Plugin file not found", respJson["message"]) | 
					
						
							| 
									
										
										
										
											2022-05-06 23:44:22 +08:00
										 |  |  | 				assert.Zero(t, l.WarnLogs.Calls) | 
					
						
							| 
									
										
										
										
											2021-09-08 14:49:05 +08:00
										 |  |  | 			}) | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("Given a request for an non-existing plugin", func(t *testing.T) { | 
					
						
							| 
									
										
										
										
											2021-11-17 19:04:22 +08:00
										 |  |  | 		service := &fakePluginStore{ | 
					
						
							|  |  |  | 			plugins: map[string]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) | 
					
						
							|  |  |  | 		pluginAssetScenario(t, "When calling GET on", url, "/public/plugins/:pluginId/*", service, l, | 
					
						
							|  |  |  | 			func(sc *scenarioContext) { | 
					
						
							|  |  |  | 				callGetPluginAsset(sc) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				var respJson map[string]interface{} | 
					
						
							|  |  |  | 				err := json.NewDecoder(sc.resp.Body).Decode(&respJson) | 
					
						
							|  |  |  | 				require.NoError(t, err) | 
					
						
							|  |  |  | 				assert.Equal(t, 404, sc.resp.Code) | 
					
						
							|  |  |  | 				assert.Equal(t, "Plugin not found", respJson["message"]) | 
					
						
							| 
									
										
										
										
											2022-05-06 23:44:22 +08:00
										 |  |  | 				assert.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) { | 
					
						
							| 
									
										
										
										
											2021-11-17 19:04:22 +08:00
										 |  |  | 		service := &fakePluginStore{ | 
					
						
							|  |  |  | 			plugins: map[string]plugins.PluginDTO{ | 
					
						
							| 
									
										
										
										
											2021-09-08 14:49:05 +08:00
										 |  |  | 				pluginID: { | 
					
						
							| 
									
										
										
										
											2021-11-01 17:53:33 +08:00
										 |  |  | 					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) | 
					
						
							|  |  |  | 		pluginAssetScenario(t, "When calling GET on", url, "/public/plugins/:pluginId/*", service, l, | 
					
						
							|  |  |  | 			func(sc *scenarioContext) { | 
					
						
							|  |  |  | 				callGetPluginAsset(sc) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 				require.Equal(t, 200, sc.resp.Code) | 
					
						
							|  |  |  | 				assert.Equal(t, expectedBody, sc.resp.Body.String()) | 
					
						
							| 
									
										
										
										
											2022-05-06 23:44:22 +08:00
										 |  |  | 				assert.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) | 
					
						
							|  |  |  | 	resp := httptest.NewRecorder() | 
					
						
							|  |  |  | 	pCtx := backend.PluginContext{} | 
					
						
							|  |  |  | 	err := hs.makePluginResourceRequest(resp, req, pCtx) | 
					
						
							|  |  |  | 	require.NoError(t, err) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for { | 
					
						
							|  |  |  | 		if resp.Flushed { | 
					
						
							|  |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	require.Equal(t, "sandbox", resp.Header().Get("Content-Security-Policy")) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											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, | 
					
						
							| 
									
										
										
										
											2021-09-08 14:49:05 +08:00
										 |  |  | 	logger log.Logger, fn scenarioFunc) { | 
					
						
							|  |  |  | 	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, | 
					
						
							|  |  |  | 			log:         logger, | 
					
						
							| 
									
										
										
										
											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-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 | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return sender.Send(&backend.CallResourceResponse{ | 
					
						
							|  |  |  | 		Status:  http.StatusOK, | 
					
						
							|  |  |  | 		Headers: make(map[string][]string), | 
					
						
							|  |  |  | 		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) { | 
					
						
							|  |  |  | 	pluginStore := fakePluginStore{plugins: map[string]plugins.PluginDTO{ | 
					
						
							|  |  |  | 		"test-app": { | 
					
						
							|  |  |  | 			PluginDir:     "/grafana/plugins/test-app/dist", | 
					
						
							|  |  |  | 			Class:         "external", | 
					
						
							|  |  |  | 			DefaultNavURL: "/plugins/test-app/page/test", | 
					
						
							|  |  |  | 			Pinned:        false, | 
					
						
							|  |  |  | 			Signature:     "unsigned", | 
					
						
							|  |  |  | 			Module:        "plugins/test-app/module", | 
					
						
							|  |  |  | 			BaseURL:       "public/plugins/test-app", | 
					
						
							|  |  |  | 			JSONData: plugins.JSONData{ | 
					
						
							|  |  |  | 				ID:   "test-app", | 
					
						
							|  |  |  | 				Type: "app", | 
					
						
							|  |  |  | 				Name: "test-app", | 
					
						
							|  |  |  | 				Info: plugins.Info{ | 
					
						
							|  |  |  | 					Version: "1.0.0", | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		"mysql": { | 
					
						
							|  |  |  | 			PluginDir: "/grafana/public/app/plugins/datasource/mysql", | 
					
						
							|  |  |  | 			Class:     "core", | 
					
						
							|  |  |  | 			Pinned:    false, | 
					
						
							|  |  |  | 			Signature: "internal", | 
					
						
							|  |  |  | 			Module:    "app/plugins/datasource/mysql/module", | 
					
						
							|  |  |  | 			BaseURL:   "public/app/plugins/datasource/mysql", | 
					
						
							|  |  |  | 			JSONData: plugins.JSONData{ | 
					
						
							|  |  |  | 				ID:   "mysql", | 
					
						
							|  |  |  | 				Type: "datasource", | 
					
						
							|  |  |  | 				Name: "MySQL", | 
					
						
							|  |  |  | 				Info: plugins.Info{ | 
					
						
							|  |  |  | 					Author:      plugins.InfoLink{Name: "Grafana Labs", URL: "https://grafana.com"}, | 
					
						
							|  |  |  | 					Description: "Data source for MySQL databases", | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 	}} | 
					
						
							|  |  |  | 	pluginSettings := fakePluginSettings{plugins: map[string]*pluginsettings.DTO{ | 
					
						
							|  |  |  | 		"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) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } |