diff --git a/pkg/api/api.go b/pkg/api/api.go index f8b1dcfee25..2d15ff1c4a8 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -310,7 +310,7 @@ func (hs *HTTPServer) registerRoutes() { apiRoute.Any("/plugins/:pluginId/resources/*", hs.CallResource) apiRoute.Get("/plugins/errors", routing.Wrap(hs.GetPluginErrorsList)) - if hs.Cfg.PluginAdminEnabled { + if hs.Cfg.PluginAdminEnabled && !hs.Cfg.PluginAdminExternalManageEnabled { apiRoute.Group("/plugins", func(pluginRoute routing.RouteRegister) { pluginRoute.Post("/:pluginId/install", routing.Wrap(hs.InstallPlugin)) pluginRoute.Post("/:pluginId/uninstall", routing.Wrap(hs.UninstallPlugin)) diff --git a/pkg/api/fakes.go b/pkg/api/fakes.go index 23b5a89c52d..f807a51781f 100644 --- a/pkg/api/fakes.go +++ b/pkg/api/fakes.go @@ -7,20 +7,18 @@ import ( ) type fakePluginStore struct { - plugins.Store - plugins map[string]plugins.PluginDTO } -func (pr fakePluginStore) Plugin(_ context.Context, pluginID string) (plugins.PluginDTO, bool) { - p, exists := pr.plugins[pluginID] +func (ps fakePluginStore) Plugin(_ context.Context, pluginID string) (plugins.PluginDTO, bool) { + p, exists := ps.plugins[pluginID] return p, exists } -func (pr fakePluginStore) Plugins(_ context.Context, pluginTypes ...plugins.Type) []plugins.PluginDTO { +func (ps fakePluginStore) Plugins(_ context.Context, pluginTypes ...plugins.Type) []plugins.PluginDTO { var result []plugins.PluginDTO - for _, v := range pr.plugins { + for _, v := range ps.plugins { for _, t := range pluginTypes { if v.Type == t { result = append(result, v) @@ -31,6 +29,23 @@ func (pr fakePluginStore) Plugins(_ context.Context, pluginTypes ...plugins.Type return result } +func (ps fakePluginStore) Add(_ context.Context, pluginID, version string) error { + ps.plugins[pluginID] = plugins.PluginDTO{ + JSONData: plugins.JSONData{ + ID: pluginID, + Info: plugins.Info{ + Version: version, + }, + }, + } + return nil +} + +func (ps fakePluginStore) Remove(_ context.Context, pluginID string) error { + delete(ps.plugins, pluginID) + return nil +} + type fakeRendererManager struct { plugins.RendererManager } diff --git a/pkg/api/plugins_test.go b/pkg/api/plugins_test.go index a28ec025750..7471e7f9e0b 100644 --- a/pkg/api/plugins_test.go +++ b/pkg/api/plugins_test.go @@ -4,11 +4,13 @@ import ( "context" "encoding/json" "fmt" + "io" "io/ioutil" "net/http" "net/http/httptest" "os" "path/filepath" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -19,8 +21,85 @@ import ( "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/web/webtest" ) +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) + } + + ps := fakePluginStore{ + plugins: make(map[string]plugins.PluginDTO), + } + for _, tc := range tcs { + srv := SetupAPITestServer(t, func(hs *HTTPServer) { + hs.Cfg = &setting.Cfg{ + PluginAdminEnabled: tc.pluginAdminEnabled, + PluginAdminExternalManageEnabled: tc.pluginAdminExternalManageEnabled, + } + hs.pluginStore = ps + }) + + t.Run(testName("Install", tc), func(t *testing.T) { + req := srv.NewPostRequest("/api/plugins/test/install", strings.NewReader("{ \"version\": \"1.0.2\" }")) + webtest.RequestWithSignedInUser(req, &models.SignedInUser{UserId: 1, OrgId: 1, OrgRole: models.ROLE_EDITOR, IsGrafanaAdmin: true}) + 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, plugins.PluginDTO{ + JSONData: plugins.JSONData{ + ID: "test", + Info: plugins.Info{ + Version: "1.0.2", + }, + }, + }, ps.plugins["test"]) + } + }) + + t.Run(testName("Uninstall", tc), func(t *testing.T) { + req := srv.NewPostRequest("/api/plugins/test/uninstall", strings.NewReader("{}")) + webtest.RequestWithSignedInUser(req, &models.SignedInUser{UserId: 1, OrgId: 1, OrgRole: models.ROLE_VIEWER, IsGrafanaAdmin: true}) + 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, ps.plugins) + } + }) + } +} + func Test_GetPluginAssets(t *testing.T) { pluginID := "test-plugin" pluginDir := "." diff --git a/pkg/tests/testinfra/testinfra.go b/pkg/tests/testinfra/testinfra.go index ad7d5f3029a..2df7d536007 100644 --- a/pkg/tests/testinfra/testinfra.go +++ b/pkg/tests/testinfra/testinfra.go @@ -262,6 +262,12 @@ func CreateGrafDir(t *testing.T, opts ...GrafanaOpts) (string, string) { _, err = anonSect.NewKey("plugin_admin_enabled", "true") require.NoError(t, err) } + if o.PluginAdminExternalManageEnabled { + anonSect, err := cfg.NewSection("plugins") + require.NoError(t, err) + _, err = anonSect.NewKey("plugin_admin_external_manage_enabled", "true") + require.NoError(t, err) + } if o.ViewersCanEdit { usersSection, err := cfg.NewSection("users") require.NoError(t, err) @@ -311,6 +317,7 @@ type GrafanaOpts struct { CatalogAppEnabled bool ViewersCanEdit bool PluginAdminEnabled bool + PluginAdminExternalManageEnabled bool AppModeProduction bool DisableLegacyAlerting bool EnableUnifiedAlerting bool