grafana/pkg/api/frontendsettings.go
Will Browne b25ea75bac
Plugins: Add secure JSON fields to plugin setting DTO (#55313)
* add secure JSON fields to plugin setting DTO

* add nil pointer fix

* adding secureJsonFields to the plugin meta.

Co-authored-by: Marcus Andersson <marcus.andersson@grafana.com>
2022-09-21 12:20:11 +02:00

494 lines
16 KiB
Go

package api
import (
"context"
"net/http"
"strconv"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/licensing"
"github.com/grafana/grafana/pkg/services/pluginsettings"
"github.com/grafana/grafana/pkg/services/secrets/kvstore"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb/grafanads"
"github.com/grafana/grafana/pkg/util"
)
func (hs *HTTPServer) GetFrontendSettings(c *models.ReqContext) {
settings, err := hs.getFrontendSettingsMap(c)
if err != nil {
c.JsonApiErr(400, "Failed to get frontend settings", err)
return
}
c.JSON(http.StatusOK, settings)
}
// getFrontendSettingsMap returns a json object with all the settings needed for front end initialisation.
func (hs *HTTPServer) getFrontendSettingsMap(c *models.ReqContext) (map[string]interface{}, error) {
enabledPlugins, err := hs.enabledPlugins(c.Req.Context(), c.OrgID)
if err != nil {
return nil, err
}
pluginsToPreload := make([]*plugins.PreloadPlugin, 0)
for _, app := range enabledPlugins[plugins.App] {
if app.Preload {
pluginsToPreload = append(pluginsToPreload, &plugins.PreloadPlugin{
Path: app.Module,
Version: app.Info.Version,
})
}
}
dataSources, err := hs.getFSDataSources(c, enabledPlugins)
if err != nil {
return nil, err
}
defaultDS := "-- Grafana --"
for n, ds := range dataSources {
if ds.IsDefault {
defaultDS = n
}
}
panels := make(map[string]plugins.PanelDTO)
for _, panel := range enabledPlugins[plugins.Panel] {
if panel.State == plugins.AlphaRelease && !hs.Cfg.PluginsEnableAlpha {
continue
}
panels[panel.ID] = plugins.PanelDTO{
ID: panel.ID,
Name: panel.Name,
Info: panel.Info,
Module: panel.Module,
BaseURL: panel.BaseURL,
SkipDataQuery: panel.SkipDataQuery,
HideFromList: panel.HideFromList,
ReleaseState: string(panel.State),
Signature: string(panel.Signature),
Sort: getPanelSort(panel.ID),
}
}
hideVersion := hs.Cfg.AnonymousHideVersion && !c.IsSignedIn
version := setting.BuildVersion
commit := setting.BuildCommit
buildstamp := setting.BuildStamp
if hideVersion {
version = ""
commit = ""
buildstamp = 0
}
hasAccess := accesscontrol.HasAccess(hs.AccessControl, c)
secretsManagerPluginEnabled := kvstore.EvaluateRemoteSecretsPlugin(c.Req.Context(), hs.secretsPluginManager, hs.Cfg) == nil
jsonObj := map[string]interface{}{
"defaultDatasource": defaultDS,
"datasources": dataSources,
"minRefreshInterval": setting.MinRefreshInterval,
"panels": panels,
"appUrl": hs.Cfg.AppURL,
"appSubUrl": hs.Cfg.AppSubURL,
"allowOrgCreate": (setting.AllowUserOrgCreate && c.IsSignedIn) || c.IsGrafanaAdmin,
"authProxyEnabled": setting.AuthProxyEnabled,
"ldapEnabled": hs.Cfg.LDAPEnabled,
"jwtHeaderName": hs.Cfg.JWTAuthHeaderName,
"jwtUrlLogin": hs.Cfg.JWTAuthURLLogin,
"alertingEnabled": setting.AlertingEnabled,
"alertingErrorOrTimeout": setting.AlertingErrorOrTimeout,
"alertingNoDataOrNullValues": setting.AlertingNoDataOrNullValues,
"alertingMinInterval": setting.AlertingMinInterval,
"liveEnabled": hs.Cfg.LiveMaxConnections != 0,
"autoAssignOrg": setting.AutoAssignOrg,
"verifyEmailEnabled": setting.VerifyEmailEnabled,
"sigV4AuthEnabled": setting.SigV4AuthEnabled,
"azureAuthEnabled": setting.AzureAuthEnabled,
"rbacEnabled": hs.Cfg.RBACEnabled,
"exploreEnabled": setting.ExploreEnabled,
"helpEnabled": setting.HelpEnabled,
"profileEnabled": setting.ProfileEnabled,
"queryHistoryEnabled": hs.Cfg.QueryHistoryEnabled,
"googleAnalyticsId": setting.GoogleAnalyticsId,
"googleAnalytics4Id": setting.GoogleAnalytics4Id,
"rudderstackWriteKey": setting.RudderstackWriteKey,
"rudderstackDataPlaneUrl": setting.RudderstackDataPlaneUrl,
"rudderstackSdkUrl": setting.RudderstackSdkUrl,
"rudderstackConfigUrl": setting.RudderstackConfigUrl,
"feedbackLinksEnabled": hs.Cfg.FeedbackLinksEnabled,
"applicationInsightsConnectionString": hs.Cfg.ApplicationInsightsConnectionString,
"applicationInsightsEndpointUrl": hs.Cfg.ApplicationInsightsEndpointUrl,
"disableLoginForm": setting.DisableLoginForm,
"disableUserSignUp": !setting.AllowUserSignUp,
"loginHint": setting.LoginHint,
"passwordHint": setting.PasswordHint,
"externalUserMngInfo": setting.ExternalUserMngInfo,
"externalUserMngLinkUrl": setting.ExternalUserMngLinkUrl,
"externalUserMngLinkName": setting.ExternalUserMngLinkName,
"viewersCanEdit": setting.ViewersCanEdit,
"angularSupportEnabled": hs.Cfg.AngularSupportEnabled,
"editorsCanAdmin": hs.Cfg.EditorsCanAdmin,
"disableSanitizeHtml": hs.Cfg.DisableSanitizeHtml,
"pluginsToPreload": pluginsToPreload,
"auth": map[string]interface{}{
"OAuthSkipOrgRoleUpdateSync": hs.Cfg.OAuthSkipOrgRoleUpdateSync,
"SAMLSkipOrgRoleSync": hs.Cfg.SectionWithEnvOverrides("auth.saml").Key("skip_org_role_sync").MustBool(false),
"DisableSyncLock": hs.Cfg.DisableSyncLock,
},
"buildInfo": map[string]interface{}{
"hideVersion": hideVersion,
"version": version,
"commit": commit,
"buildstamp": buildstamp,
"edition": hs.License.Edition(),
"latestVersion": hs.grafanaUpdateChecker.LatestVersion(),
"hasUpdate": hs.grafanaUpdateChecker.UpdateAvailable(),
"env": setting.Env,
},
"licenseInfo": map[string]interface{}{
"expiry": hs.License.Expiry(),
"stateInfo": hs.License.StateInfo(),
"licenseUrl": hs.License.LicenseURL(hasAccess(accesscontrol.ReqGrafanaAdmin, licensing.PageAccess)),
"edition": hs.License.Edition(),
"enabledFeatures": hs.License.EnabledFeatures(),
},
"featureToggles": hs.Features.GetEnabled(c.Req.Context()),
"rendererAvailable": hs.RenderService.IsAvailable(c.Req.Context()),
"rendererVersion": hs.RenderService.Version(),
"secretsManagerPluginEnabled": secretsManagerPluginEnabled,
"http2Enabled": hs.Cfg.Protocol == setting.HTTP2Scheme,
"sentry": hs.Cfg.Sentry,
"grafanaJavascriptAgent": hs.Cfg.GrafanaJavascriptAgent,
"pluginCatalogURL": hs.Cfg.PluginCatalogURL,
"pluginAdminEnabled": hs.Cfg.PluginAdminEnabled,
"pluginAdminExternalManageEnabled": hs.Cfg.PluginAdminEnabled && hs.Cfg.PluginAdminExternalManageEnabled,
"pluginCatalogHiddenPlugins": hs.Cfg.PluginCatalogHiddenPlugins,
"expressionsEnabled": hs.Cfg.ExpressionsEnabled,
"awsAllowedAuthProviders": hs.Cfg.AWSAllowedAuthProviders,
"awsAssumeRoleEnabled": hs.Cfg.AWSAssumeRoleEnabled,
"azure": map[string]interface{}{
"cloud": hs.Cfg.Azure.Cloud,
"managedIdentityEnabled": hs.Cfg.Azure.ManagedIdentityEnabled,
},
"caching": map[string]bool{
"enabled": hs.Cfg.SectionWithEnvOverrides("caching").Key("enabled").MustBool(true),
},
"recordedQueries": map[string]bool{
"enabled": hs.Cfg.SectionWithEnvOverrides("recorded_queries").Key("enabled").MustBool(true),
},
"reporting": map[string]bool{
"enabled": hs.Cfg.SectionWithEnvOverrides("reporting").Key("enabled").MustBool(true),
},
"unifiedAlertingEnabled": hs.Cfg.UnifiedAlerting.Enabled,
"unifiedAlerting": map[string]interface{}{
"minInterval": hs.Cfg.UnifiedAlerting.MinInterval.String(),
},
}
if hs.ThumbService != nil {
jsonObj["dashboardPreviews"] = hs.ThumbService.GetDashboardPreviewsSetupSettings(c)
}
if hs.Cfg.GeomapDefaultBaseLayerConfig != nil {
jsonObj["geomapDefaultBaseLayerConfig"] = hs.Cfg.GeomapDefaultBaseLayerConfig
}
if !hs.Cfg.GeomapEnableCustomBaseLayers {
jsonObj["geomapDisableCustomBaseLayer"] = true
}
return jsonObj, nil
}
func (hs *HTTPServer) getFSDataSources(c *models.ReqContext, enabledPlugins EnabledPlugins) (map[string]plugins.DataSourceDTO, error) {
orgDataSources := make([]*datasources.DataSource, 0)
if c.OrgID != 0 {
query := datasources.GetDataSourcesQuery{OrgId: c.OrgID, DataSourceLimit: hs.Cfg.DataSourceLimit}
err := hs.DataSourcesService.GetDataSources(c.Req.Context(), &query)
if err != nil {
return nil, err
}
filtered, err := hs.filterDatasourcesByQueryPermission(c.Req.Context(), c.SignedInUser, query.Result)
if err != nil {
return nil, err
}
orgDataSources = filtered
}
dataSources := make(map[string]plugins.DataSourceDTO)
for _, ds := range orgDataSources {
url := ds.Url
if ds.Access == datasources.DS_ACCESS_PROXY {
url = "/api/datasources/proxy/" + strconv.FormatInt(ds.Id, 10)
}
dsDTO := plugins.DataSourceDTO{
ID: ds.Id,
UID: ds.Uid,
Type: ds.Type,
Name: ds.Name,
URL: url,
IsDefault: ds.IsDefault,
Access: string(ds.Access),
ReadOnly: ds.ReadOnly,
}
plugin, exists := enabledPlugins.Get(plugins.DataSource, ds.Type)
if !exists {
c.Logger.Error("Could not find plugin definition for data source", "datasource_type", ds.Type)
continue
}
dsDTO.Preload = plugin.Preload
dsDTO.Module = plugin.Module
dsDTO.PluginMeta = &plugins.PluginMetaDTO{
JSONData: plugin.JSONData,
Signature: plugin.Signature,
Module: plugin.Module,
BaseURL: plugin.BaseURL,
}
if ds.JsonData == nil {
dsDTO.JSONData = make(map[string]interface{})
} else {
dsDTO.JSONData = ds.JsonData.MustMap()
}
if ds.Access == datasources.DS_ACCESS_DIRECT {
if ds.BasicAuth {
password, err := hs.DataSourcesService.DecryptedBasicAuthPassword(c.Req.Context(), ds)
if err != nil {
return nil, err
}
dsDTO.BasicAuth = util.GetBasicAuthHeader(
ds.BasicAuthUser,
password,
)
}
if ds.WithCredentials {
dsDTO.WithCredentials = ds.WithCredentials
}
if ds.Type == datasources.DS_INFLUXDB_08 {
password, err := hs.DataSourcesService.DecryptedPassword(c.Req.Context(), ds)
if err != nil {
return nil, err
}
dsDTO.Username = ds.User
dsDTO.Password = password
dsDTO.URL = url + "/db/" + ds.Database
}
if ds.Type == datasources.DS_INFLUXDB {
password, err := hs.DataSourcesService.DecryptedPassword(c.Req.Context(), ds)
if err != nil {
return nil, err
}
dsDTO.Username = ds.User
dsDTO.Password = password
dsDTO.URL = url
}
}
if (ds.Type == datasources.DS_INFLUXDB) || (ds.Type == datasources.DS_ES) {
dsDTO.Database = ds.Database
}
if ds.Type == datasources.DS_PROMETHEUS {
// add unproxied server URL for link to Prometheus web UI
ds.JsonData.Set("directUrl", ds.Url)
}
dataSources[ds.Name] = dsDTO
}
// add data sources that are built in (meaning they are not added via data sources page, nor have any entry in
// the datasource table)
for _, ds := range hs.pluginStore.Plugins(c.Req.Context(), plugins.DataSource) {
if ds.BuiltIn {
dto := plugins.DataSourceDTO{
Type: string(ds.Type),
Name: ds.Name,
JSONData: make(map[string]interface{}),
PluginMeta: &plugins.PluginMetaDTO{
JSONData: ds.JSONData,
Signature: ds.Signature,
Module: ds.Module,
BaseURL: ds.BaseURL,
},
}
if ds.Name == grafanads.DatasourceName {
dto.ID = grafanads.DatasourceID
dto.UID = grafanads.DatasourceUID
}
dataSources[ds.Name] = dto
}
}
return dataSources, nil
}
func getPanelSort(id string) int {
sort := 100
switch id {
case "timeseries":
sort = 1
case "barchart":
sort = 2
case "stat":
sort = 3
case "gauge":
sort = 4
case "bargauge":
sort = 5
case "table":
sort = 6
case "singlestat":
sort = 7
case "piechart":
sort = 8
case "state-timeline":
sort = 9
case "heatmap":
sort = 10
case "status-history":
sort = 11
case "histogram":
sort = 12
case "graph":
sort = 13
case "text":
sort = 14
case "alertlist":
sort = 15
case "dashlist":
sort = 16
case "news":
sort = 17
}
return sort
}
// EnabledPlugins represents a mapping from plugin types (panel, data source, etc.) to plugin IDs to plugins
// For example ["panel"] -> ["piechart"] -> {pie chart plugin DTO}
type EnabledPlugins map[plugins.Type]map[string]plugins.PluginDTO
func (ep EnabledPlugins) Get(pluginType plugins.Type, pluginID string) (plugins.PluginDTO, bool) {
if _, exists := ep[pluginType][pluginID]; exists {
return ep[pluginType][pluginID], true
}
return plugins.PluginDTO{}, false
}
func (hs *HTTPServer) enabledPlugins(ctx context.Context, orgID int64) (EnabledPlugins, error) {
ep := make(EnabledPlugins)
pluginSettingMap, err := hs.pluginSettings(ctx, orgID)
if err != nil {
return ep, err
}
apps := make(map[string]plugins.PluginDTO)
for _, app := range hs.pluginStore.Plugins(ctx, plugins.App) {
if b, exists := pluginSettingMap[app.ID]; exists {
app.Pinned = b.Pinned
apps[app.ID] = app
}
}
ep[plugins.App] = apps
dataSources := make(map[string]plugins.PluginDTO)
for _, ds := range hs.pluginStore.Plugins(ctx, plugins.DataSource) {
if _, exists := pluginSettingMap[ds.ID]; exists {
dataSources[ds.ID] = ds
}
}
ep[plugins.DataSource] = dataSources
panels := make(map[string]plugins.PluginDTO)
for _, p := range hs.pluginStore.Plugins(ctx, plugins.Panel) {
if _, exists := pluginSettingMap[p.ID]; exists {
panels[p.ID] = p
}
}
ep[plugins.Panel] = panels
return ep, nil
}
func (hs *HTTPServer) pluginSettings(ctx context.Context, orgID int64) (map[string]*pluginsettings.InfoDTO, error) {
pluginSettings := make(map[string]*pluginsettings.InfoDTO)
// fill settings from database
if pss, err := hs.PluginSettings.GetPluginSettings(ctx, &pluginsettings.GetArgs{OrgID: orgID}); err != nil {
return nil, err
} else {
for _, ps := range pss {
pluginSettings[ps.PluginID] = ps
}
}
// fill settings from app plugins
for _, plugin := range hs.pluginStore.Plugins(ctx, plugins.App) {
// ignore settings that already exist
if _, exists := pluginSettings[plugin.ID]; exists {
continue
}
// add new setting which is enabled depending on if AutoEnabled: true
pluginSetting := &pluginsettings.InfoDTO{
PluginID: plugin.ID,
OrgID: orgID,
Enabled: plugin.AutoEnabled,
Pinned: plugin.AutoEnabled,
PluginVersion: plugin.Info.Version,
}
pluginSettings[plugin.ID] = pluginSetting
}
// fill settings from all remaining plugins (including potential app child plugins)
for _, plugin := range hs.pluginStore.Plugins(ctx) {
// ignore settings that already exist
if _, exists := pluginSettings[plugin.ID]; exists {
continue
}
// add new setting which is enabled by default
pluginSetting := &pluginsettings.InfoDTO{
PluginID: plugin.ID,
OrgID: orgID,
Enabled: true,
Pinned: false,
PluginVersion: plugin.Info.Version,
}
// if plugin is included in an app, check app settings
if plugin.IncludedInAppID != "" {
// app child plugins are disabled unless app is enabled
pluginSetting.Enabled = false
if p, exists := pluginSettings[plugin.IncludedInAppID]; exists {
pluginSetting.Enabled = p.Enabled
}
}
pluginSettings[plugin.ID] = pluginSetting
}
return pluginSettings, nil
}