diff --git a/pkg/api/plugins.go b/pkg/api/plugins.go index d7bc8f1fea2..ffe15c59357 100644 --- a/pkg/api/plugins.go +++ b/pkg/api/plugins.go @@ -173,6 +173,11 @@ func (hs *HTTPServer) GetPluginList(c *contextmodel.ReqContext) response.Respons func (hs *HTTPServer) GetPluginSettingByID(c *contextmodel.ReqContext) response.Response { pluginID := web.Params(c.Req)[":pluginId"] + perr := hs.pluginErrorResolver.PluginError(c.Req.Context(), pluginID) + if perr != nil { + return response.Error(http.StatusInternalServerError, perr.PublicMessage(), perr) + } + plugin, exists := hs.pluginStore.Plugin(c.Req.Context(), pluginID) if !exists { return response.Error(http.StatusNotFound, "Plugin not found, no installed plugin with that id", nil) diff --git a/pkg/api/plugins_test.go b/pkg/api/plugins_test.go index 4731b2fb032..a948a261013 100644 --- a/pkg/api/plugins_test.go +++ b/pkg/api/plugins_test.go @@ -38,6 +38,7 @@ import ( "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/org/orgtest" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginaccesscontrol" + "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginerrs" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore" "github.com/grafana/grafana/pkg/services/updatechecker" @@ -764,3 +765,88 @@ func TestHTTPServer_hasPluginRequestedPermissions(t *testing.T) { }) } } + +func Test_PluginsSettings(t *testing.T) { + pID := "test-datasource" + p1 := createPlugin(plugins.JSONData{ + ID: pID, Type: "datasource", Name: pID, + Info: plugins.Info{ + Version: "1.0.0", + }}, plugins.ClassExternal, plugins.NewFakeFS()) + + pluginRegistry := &fakes.FakePluginRegistry{ + Store: map[string]*plugins.Plugin{ + p1.ID: p1, + }, + } + + pluginSettings := pluginsettings.FakePluginSettings{Plugins: map[string]*pluginsettings.DTO{ + pID: {ID: 0, OrgID: 1, PluginID: pID, PluginVersion: "1.0.0"}, + }} + + type testCase struct { + desc string + expectedCode int + errCode plugins.ErrorCode + expectedSettings dtos.PluginSetting + expectedError string + } + tcs := []testCase{ + { + desc: "should only be able to get plugin settings", + expectedCode: http.StatusOK, + expectedSettings: dtos.PluginSetting{ + Id: pID, + Name: pID, + Type: "datasource", + Info: plugins.Info{ + Version: "1.0.0", + }, + SecureJsonFields: map[string]bool{}, + }, + }, + { + desc: "should return a plugin error", + expectedCode: http.StatusInternalServerError, + errCode: plugins.ErrorCodeFailedBackendStart, + expectedError: "Plugin failed to start", + }, + } + + for _, tc := range tcs { + t.Run(tc.desc, func(t *testing.T) { + server := SetupAPITestServer(t, func(hs *HTTPServer) { + hs.Cfg = setting.NewCfg() + hs.PluginSettings = &pluginSettings + hs.pluginStore = pluginstore.New(pluginRegistry, &fakes.FakeLoader{}) + hs.pluginFileStore = filestore.ProvideService(pluginRegistry) + errTracker := pluginerrs.ProvideErrorTracker() + if tc.errCode != "" { + errTracker.Record(context.Background(), &plugins.Error{ + PluginID: pID, + ErrorCode: tc.errCode, + }) + } + hs.pluginErrorResolver = pluginerrs.ProvideStore(errTracker) + var err error + hs.pluginsUpdateChecker, err = updatechecker.ProvidePluginsService(hs.Cfg, nil, tracing.InitializeTracerForTest()) + require.NoError(t, err) + }) + + res, err := server.Send(webtest.RequestWithSignedInUser(server.NewGetRequest("/api/plugins/"+pID+"/settings"), userWithPermissions(1, []ac.Permission{}))) + require.NoError(t, err) + assert.Equal(t, tc.expectedCode, res.StatusCode) + if tc.expectedCode == http.StatusOK { + var result dtos.PluginSetting + require.NoError(t, json.NewDecoder(res.Body).Decode(&result)) + require.NoError(t, res.Body.Close()) + assert.Equal(t, tc.expectedSettings, result) + } else { + var respJson map[string]any + err := json.NewDecoder(res.Body).Decode(&respJson) + require.NoError(t, err) + require.Equal(t, tc.expectedError, respJson["message"]) + } + }) + } +} diff --git a/pkg/plugins/ifaces.go b/pkg/plugins/ifaces.go index aa3dcd0180b..73898c981e0 100644 --- a/pkg/plugins/ifaces.go +++ b/pkg/plugins/ifaces.go @@ -110,6 +110,7 @@ type StaticRouteResolver interface { type ErrorResolver interface { PluginErrors(ctx context.Context) []*Error + PluginError(ctx context.Context, pluginID string) *Error } type PluginLoaderAuthorizer interface { diff --git a/pkg/plugins/models.go b/pkg/plugins/models.go index 0b76206fe08..a7583ef4a9c 100644 --- a/pkg/plugins/models.go +++ b/pkg/plugins/models.go @@ -296,6 +296,23 @@ func (e *Error) WithMessage(m string) *Error { return e } +func (e Error) PublicMessage() string { + switch e.ErrorCode { + case errorCodeSignatureInvalid: + return "Invalid plugin signature" + case errorCodeSignatureModified: + return "Plugin signature does not match" + case errorCodeSignatureMissing: + return "Plugin signature is missing" + case ErrorCodeFailedBackendStart: + return "Plugin failed to start" + case ErrorAngular: + return "Angular plugins are not supported" + } + + return "Plugin failed to load" +} + // Access-Control related definitions // RoleRegistration stores a role and its assignments to basic roles diff --git a/pkg/services/pluginsintegration/loader/fakes.go b/pkg/services/pluginsintegration/loader/fakes.go index 108882835ab..2a6809deb3c 100644 --- a/pkg/services/pluginsintegration/loader/fakes.go +++ b/pkg/services/pluginsintegration/loader/fakes.go @@ -36,3 +36,7 @@ func (t *fakeErrorTracker) Errors(ctx context.Context) []*plugins.Error { } return nil } + +func (t *fakeErrorTracker) Error(ctx context.Context, pluginID string) *plugins.Error { + return &plugins.Error{} +} diff --git a/pkg/services/pluginsintegration/pipeline/pipeline.go b/pkg/services/pluginsintegration/pipeline/pipeline.go index 8a49857047a..f45cb3bfd76 100644 --- a/pkg/services/pluginsintegration/pipeline/pipeline.go +++ b/pkg/services/pluginsintegration/pipeline/pipeline.go @@ -65,10 +65,10 @@ func ProvideInitializationStage(cfg *config.PluginManagementCfg, pr registry.Ser InitializeFuncs: []initialization.InitializeFunc{ ExternalServiceRegistrationStep(cfg, externalServiceRegistry, tracer), initialization.BackendClientInitStep(pluginEnvProvider, bp), - initialization.PluginRegistrationStep(pr), initialization.BackendProcessStartStep(pm), RegisterPluginRolesStep(roleRegistry), ReportBuildMetrics, + initialization.PluginRegistrationStep(pr), }, }) } diff --git a/pkg/services/pluginsintegration/pluginerrs/errors.go b/pkg/services/pluginsintegration/pluginerrs/errors.go index 3cf2a08e090..026a0a121af 100644 --- a/pkg/services/pluginsintegration/pluginerrs/errors.go +++ b/pkg/services/pluginsintegration/pluginerrs/errors.go @@ -28,6 +28,16 @@ func (s *Store) PluginErrors(ctx context.Context) []*plugins.Error { return errs } +func (s *Store) PluginError(ctx context.Context, pluginID string) *plugins.Error { + err := s.errs.Error(ctx, pluginID) + if err == nil { + return nil + } + + err.ErrorCode = err.AsErrorCode() + return err +} + type ErrorRegistry struct { errs map[string]*plugins.Error log log.Logger @@ -37,6 +47,7 @@ type ErrorTracker interface { Record(ctx context.Context, err *plugins.Error) Clear(ctx context.Context, pluginID string) Errors(ctx context.Context) []*plugins.Error + Error(ctx context.Context, pluginID string) *plugins.Error } func ProvideErrorTracker() *ErrorRegistry { @@ -65,3 +76,7 @@ func (r *ErrorRegistry) Errors(_ context.Context) []*plugins.Error { } return errs } + +func (r *ErrorRegistry) Error(_ context.Context, pluginID string) *plugins.Error { + return r.errs[pluginID] +}