Rendering: Remove SVG sanitization (#109797)

This commit is contained in:
Mariell Hoversholm
2025-08-19 11:34:12 +02:00
committed by GitHub
parent 1a87679dc7
commit 0fc29cbaae
15 changed files with 49 additions and 344 deletions
+1 -1
View File
@@ -193,6 +193,7 @@ require (
go.opentelemetry.io/otel/trace v1.37.0 // @grafana/grafana-backend-group
go.uber.org/atomic v1.11.0 // @grafana/alerting-backend
go.uber.org/goleak v1.3.0 // @grafana/grafana-search-and-storage
go.uber.org/mock v0.5.2 // @grafana/grafana-operator-experience-squad
go.uber.org/zap v1.27.0 // @grafana/identity-access-team
gocloud.dev v0.42.0 // @grafana/grafana-app-platform-squad
gocloud.dev/secrets/hashivault v0.42.0 // @grafana/grafana-operator-experience-squad
@@ -599,7 +600,6 @@ require (
go.opentelemetry.io/otel/sdk/log v0.12.2 // indirect
go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect
go.opentelemetry.io/proto/otlp v1.7.0 // indirect
go.uber.org/mock v0.5.2 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
go4.org/netipx v0.0.0-20230125063823-8449b0a6169f // indirect
+1 -1
View File
@@ -27,7 +27,6 @@ import (
_ "github.com/robfig/cron/v3"
_ "github.com/russellhaering/goxmldsig"
_ "github.com/spf13/cobra" // used by the standalone apiserver cli
_ "github.com/spyzhov/ajson"
_ "github.com/stretchr/testify/require"
_ "gocloud.dev/secrets/awskms"
_ "gocloud.dev/secrets/azurekeyvault"
@@ -53,4 +52,5 @@ import (
_ "github.com/grafana/e2e"
_ "github.com/grafana/gofpdf"
_ "github.com/grafana/gomemcache/memcache"
_ "github.com/spyzhov/ajson"
)
@@ -8,9 +8,9 @@ import (
"path/filepath"
"testing"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/models"
@@ -43,7 +43,6 @@ import (
"github.com/grafana/grafana/pkg/services/ssosettings"
"github.com/grafana/grafana/pkg/services/ssosettings/ssosettingsimpl"
"github.com/grafana/grafana/pkg/services/store"
"github.com/grafana/grafana/pkg/services/store/sanitizer"
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlesimpl"
"github.com/grafana/grafana/pkg/services/team/teamapi"
"github.com/grafana/grafana/pkg/services/updatemanager"
@@ -73,7 +72,6 @@ func ProvideBackgroundServiceRegistry(
// Need to make sure these are initialized, is there a better place to put them?
_ dashboardsnapshots.Service,
_ serviceaccounts.Service,
_ *sanitizer.Provider,
_ *grpcserver.HealthService, _ *grpcserver.ReflectionService,
_ *ldapapi.Service, _ *apiregistry.Service, _ auth.IDService, _ *teamapi.TeamAPI, _ ssosettings.Service,
_ cloudmigration.Service, _ authnimpl.Registration,
-2
View File
@@ -157,7 +157,6 @@ import (
"github.com/grafana/grafana/pkg/services/stats/statsimpl"
"github.com/grafana/grafana/pkg/services/store"
"github.com/grafana/grafana/pkg/services/store/resolver"
"github.com/grafana/grafana/pkg/services/store/sanitizer"
"github.com/grafana/grafana/pkg/services/supportbundles"
"github.com/grafana/grafana/pkg/services/supportbundles/bundleregistry"
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlesimpl"
@@ -349,7 +348,6 @@ var wireBasicSet = wire.NewSet(
plugindashboardsservice.ProvideService,
wire.Bind(new(plugindashboards.Service), new(*plugindashboardsservice.Service)),
plugindashboardsservice.ProvideDashboardUpdater,
sanitizer.ProvideService,
secretsStore.ProvideService,
avatar.ProvideAvatarCacheServer,
statscollector.ProvideService,
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -6,8 +6,8 @@ import (
"testing"
"time"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"go.uber.org/mock/gomock"
claims "github.com/grafana/authlib/types"
-1
View File
@@ -18,7 +18,6 @@ type CapabilityName string
const (
ScalingDownImages CapabilityName = "ScalingDownImages"
FullHeightImages CapabilityName = "FullHeightImages"
SVGSanitization CapabilityName = "SvgSanitization"
PDFRendering CapabilityName = "PdfRendering"
)
+2 -13
View File
@@ -74,15 +74,6 @@ type ErrorOpts struct {
ErrorRenderUnavailable bool
}
type SanitizeSVGRequest struct {
Filename string
Content []byte
}
type SanitizeSVGResponse struct {
Sanitized []byte
}
type Result struct {
FilePath string
FileName string
@@ -99,7 +90,6 @@ type RenderCSVResult struct {
type renderFunc func(ctx context.Context, renderType RenderType, renderKey string, options Opts) (*RenderResult, error)
type renderCSVFunc func(ctx context.Context, renderKey string, options CSVOpts) (*RenderCSVResult, error)
type sanitizeFunc func(ctx context.Context, req *SanitizeSVGRequest) (*SanitizeSVGResponse, error)
type renderKeyProvider interface {
get(ctx context.Context, opts AuthOpts) (string, error)
@@ -121,16 +111,15 @@ type CapabilitySupportRequestResult struct {
SemverConstraint string
}
//go:generate mockgen -destination=mock.go -package=rendering github.com/grafana/grafana/pkg/services/rendering Service
//go:generate go run go.uber.org/mock/mockgen@v0.5.2 -destination=mock.go -package=rendering github.com/grafana/grafana/pkg/services/rendering Service
type Service interface {
IsAvailable(ctx context.Context) bool
Version() string
Render(ctx context.Context, renderType RenderType, opts Opts, session Session) (*RenderResult, error)
RenderCSV(ctx context.Context, opts CSVOpts, session Session) (*RenderCSVResult, error)
RenderErrorImage(theme models.Theme, error error) (*RenderResult, error)
RenderErrorImage(theme models.Theme, err error) (*RenderResult, error)
GetRenderUser(ctx context.Context, key string) (*RenderUser, bool)
HasCapability(ctx context.Context, capability CapabilityName) (CapabilitySupportRequestResult, error)
IsCapabilitySupported(ctx context.Context, capability CapabilityName) error
CreateRenderingSession(ctx context.Context, authOpts AuthOpts, sessionOpts SessionOpts) (Session, error)
SanitizeSVG(ctx context.Context, req *SanitizeSVGRequest) (*SanitizeSVGResponse, error)
}
+30 -39
View File
@@ -1,21 +1,27 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/grafana/grafana/pkg/services/rendering (interfaces: Service)
//
// Generated by this command:
//
// mockgen -destination=mock.go -package=rendering github.com/grafana/grafana/pkg/services/rendering Service
//
// Package rendering is a generated GoMock package.
package rendering
import (
context "context"
reflect "reflect"
gomock "github.com/golang/mock/gomock"
models "github.com/grafana/grafana/pkg/models"
gomock "go.uber.org/mock/gomock"
)
// MockService is a mock of Service interface.
type MockService struct {
ctrl *gomock.Controller
recorder *MockServiceMockRecorder
isgomock struct{}
}
// MockServiceMockRecorder is the mock recorder for MockService.
@@ -45,7 +51,7 @@ func (m *MockService) CreateRenderingSession(ctx context.Context, authOpts AuthO
}
// CreateRenderingSession indicates an expected call of CreateRenderingSession.
func (mr *MockServiceMockRecorder) CreateRenderingSession(ctx, authOpts, sessionOpts interface{}) *gomock.Call {
func (mr *MockServiceMockRecorder) CreateRenderingSession(ctx, authOpts, sessionOpts any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateRenderingSession", reflect.TypeOf((*MockService)(nil).CreateRenderingSession), ctx, authOpts, sessionOpts)
}
@@ -60,7 +66,7 @@ func (m *MockService) GetRenderUser(ctx context.Context, key string) (*RenderUse
}
// GetRenderUser indicates an expected call of GetRenderUser.
func (mr *MockServiceMockRecorder) GetRenderUser(ctx, key interface{}) *gomock.Call {
func (mr *MockServiceMockRecorder) GetRenderUser(ctx, key any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetRenderUser", reflect.TypeOf((*MockService)(nil).GetRenderUser), ctx, key)
}
@@ -75,25 +81,11 @@ func (m *MockService) HasCapability(ctx context.Context, capability CapabilityNa
}
// HasCapability indicates an expected call of HasCapability.
func (mr *MockServiceMockRecorder) HasCapability(ctx, capability interface{}) *gomock.Call {
func (mr *MockServiceMockRecorder) HasCapability(ctx, capability any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasCapability", reflect.TypeOf((*MockService)(nil).HasCapability), ctx, capability)
}
// IsCapabilitySupported mocks base method.
func (m *MockService) IsCapabilitySupported(ctx context.Context, capability CapabilityName) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "IsCapabilitySupported", ctx, capability)
ret0, _ := ret[0].(error)
return ret0
}
// IsCapabilitySupported indicates an expected call of IsCapabilitySupported.
func (mr *MockServiceMockRecorder) IsCapabilitySupported(ctx, capability interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsCapabilitySupported", reflect.TypeOf((*MockService)(nil).IsCapabilitySupported), ctx, capability)
}
// IsAvailable mocks base method.
func (m *MockService) IsAvailable(ctx context.Context) bool {
m.ctrl.T.Helper()
@@ -103,11 +95,25 @@ func (m *MockService) IsAvailable(ctx context.Context) bool {
}
// IsAvailable indicates an expected call of IsAvailable.
func (mr *MockServiceMockRecorder) IsAvailable(ctx interface{}) *gomock.Call {
func (mr *MockServiceMockRecorder) IsAvailable(ctx any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsAvailable", reflect.TypeOf((*MockService)(nil).IsAvailable), ctx)
}
// IsCapabilitySupported mocks base method.
func (m *MockService) IsCapabilitySupported(ctx context.Context, capability CapabilityName) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "IsCapabilitySupported", ctx, capability)
ret0, _ := ret[0].(error)
return ret0
}
// IsCapabilitySupported indicates an expected call of IsCapabilitySupported.
func (mr *MockServiceMockRecorder) IsCapabilitySupported(ctx, capability any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsCapabilitySupported", reflect.TypeOf((*MockService)(nil).IsCapabilitySupported), ctx, capability)
}
// Render mocks base method.
func (m *MockService) Render(ctx context.Context, renderType RenderType, opts Opts, session Session) (*RenderResult, error) {
m.ctrl.T.Helper()
@@ -118,7 +124,7 @@ func (m *MockService) Render(ctx context.Context, renderType RenderType, opts Op
}
// Render indicates an expected call of Render.
func (mr *MockServiceMockRecorder) Render(ctx, renderType, opts, session interface{}) *gomock.Call {
func (mr *MockServiceMockRecorder) Render(ctx, renderType, opts, session any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Render", reflect.TypeOf((*MockService)(nil).Render), ctx, renderType, opts, session)
}
@@ -133,7 +139,7 @@ func (m *MockService) RenderCSV(ctx context.Context, opts CSVOpts, session Sessi
}
// RenderCSV indicates an expected call of RenderCSV.
func (mr *MockServiceMockRecorder) RenderCSV(ctx, opts, session interface{}) *gomock.Call {
func (mr *MockServiceMockRecorder) RenderCSV(ctx, opts, session any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RenderCSV", reflect.TypeOf((*MockService)(nil).RenderCSV), ctx, opts, session)
}
@@ -148,24 +154,9 @@ func (m *MockService) RenderErrorImage(theme models.Theme, err error) (*RenderRe
}
// RenderErrorImage indicates an expected call of RenderErrorImage.
func (mr *MockServiceMockRecorder) RenderErrorImage(theme, error interface{}) *gomock.Call {
func (mr *MockServiceMockRecorder) RenderErrorImage(theme, err any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RenderErrorImage", reflect.TypeOf((*MockService)(nil).RenderErrorImage), theme, error)
}
// SanitizeSVG mocks base method.
func (m *MockService) SanitizeSVG(ctx context.Context, req *SanitizeSVGRequest) (*SanitizeSVGResponse, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SanitizeSVG", ctx, req)
ret0, _ := ret[0].(*SanitizeSVGResponse)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// SanitizeSVG indicates an expected call of SanitizeSVG.
func (mr *MockServiceMockRecorder) SanitizeSVG(ctx, req interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SanitizeSVG", reflect.TypeOf((*MockService)(nil).SanitizeSVG), ctx, req)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RenderErrorImage", reflect.TypeOf((*MockService)(nil).RenderErrorImage), theme, err)
}
// Version mocks base method.
+3 -42
View File
@@ -31,8 +31,6 @@ type RenderingService struct {
plugin Plugin
renderAction renderFunc
renderCSVAction renderCSVFunc
sanitizeSVGAction sanitizeFunc
sanitizeURL string
domain string
inProgressCount int32
version string
@@ -75,21 +73,14 @@ func ProvideService(cfg *setting.Cfg, features featuremgmt.FeatureToggles, remot
logger := log.New("rendering")
// URL for HTTP sanitize API
var sanitizeURL string
// value used for domain attribute of renderKey cookie
var domain string
// value used by the image renderer to make requests to Grafana
rendererCallbackURL := cfg.RendererCallbackUrl
if cfg.RendererServerUrl != "" {
sanitizeURL = getSanitizerURL(cfg.RendererServerUrl)
// Default value for callback URL using a remote renderer should be AppURL
if rendererCallbackURL == "" {
rendererCallbackURL = cfg.AppURL
}
// Default value for callback URL using a remote renderer should be AppURL
if cfg.RendererServerUrl != "" && rendererCallbackURL == "" {
rendererCallbackURL = cfg.AppURL
}
switch {
@@ -140,10 +131,6 @@ func ProvideService(cfg *setting.Cfg, features featuremgmt.FeatureToggles, remot
name: ScalingDownImages,
semverConstraint: ">= 3.4.0",
},
{
name: SVGSanitization,
semverConstraint: ">= 3.5.0",
},
{
name: PDFRendering,
semverConstraint: ">= 3.10.0",
@@ -155,7 +142,6 @@ func ProvideService(cfg *setting.Cfg, features featuremgmt.FeatureToggles, remot
RendererPluginManager: rm,
log: logger,
domain: domain,
sanitizeURL: sanitizeURL,
pluginAvailable: exists,
rendererCallbackURL: rendererCallbackURL,
}
@@ -165,11 +151,6 @@ func ProvideService(cfg *setting.Cfg, features featuremgmt.FeatureToggles, remot
return s, nil
}
func getSanitizerURL(rendererURL string) string {
rendererBaseURL := strings.TrimSuffix(rendererURL, "/render")
return rendererBaseURL + "/sanitize"
}
func (rs *RenderingService) Run(ctx context.Context) error {
if rs.remoteAvailable() {
rs.log = rs.log.New("renderer", "http")
@@ -188,7 +169,6 @@ func (rs *RenderingService) Run(ctx context.Context) error {
})
rs.renderAction = rs.renderViaHTTP
rs.renderCSVAction = rs.renderCSVViaHTTP
rs.sanitizeSVGAction = rs.sanitizeViaHTTP
refreshTicker := time.NewTicker(remoteVersionRefreshInterval)
@@ -213,7 +193,6 @@ func (rs *RenderingService) Run(ctx context.Context) error {
rs.version = rp.Version()
rs.renderAction = rs.renderViaPlugin
rs.renderCSVAction = rs.renderCSVViaPlugin
rs.sanitizeSVGAction = rs.sanitizeSVGViaPlugin
<-ctx.Done()
return nil
@@ -367,24 +346,6 @@ func (rs *RenderingService) RenderCSV(ctx context.Context, opts CSVOpts, session
return result, err
}
func (rs *RenderingService) SanitizeSVG(ctx context.Context, req *SanitizeSVGRequest) (*SanitizeSVGResponse, error) {
capability, err := rs.HasCapability(ctx, SVGSanitization)
if err != nil {
return nil, err
}
if !capability.IsSupported {
return nil, fmt.Errorf("svg sanitization unsupported, requires image renderer version: %s", capability.SemverConstraint)
}
start := time.Now()
action, err := rs.sanitizeSVGAction(ctx, req)
rs.log.Info("svg sanitization finished", "duration", time.Since(start), "filename", req.Filename, "isError", err != nil)
return action, err
}
func (rs *RenderingService) renderCSV(ctx context.Context, opts CSVOpts, renderKeyProvider renderKeyProvider) (*RenderCSVResult, error) {
logger := rs.log.FromContext(ctx)
-182
View File
@@ -1,182 +0,0 @@
package rendering
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"mime/multipart"
"net/http"
"net/textproto"
"net/url"
"time"
"github.com/grafana/grafana/pkg/plugins/backendplugin/pluginextensionv2"
)
var (
domPurifySvgConfig = map[string]any{
// domPurifyConfig is passed directly to DOMPurify https://github.com/cure53/DOMPurify#can-i-configure-dompurify
"domPurifyConfig": map[string]any{
"USE_PROFILES": map[string]bool{"svg": true, "svgFilters": true},
"ADD_TAGS": []string{"use"},
},
// allowAllLinksInSvgUseTags will preserve all `use` tags.
// By default, we remove all non-self-referential `use` tags, i.e. those which `href` attribute does not start with `#`
"allowAllLinksInSvgUseTags": false,
}
domPurifyConfigType = "DOMPurify"
)
type formFile struct {
fileName string
key string
contentType string
content io.Reader
}
func createMultipartRequestBody(values []formFile) (bytes.Buffer, string, error) {
var b bytes.Buffer
w := multipart.NewWriter(&b)
for _, f := range values {
h := make(textproto.MIMEHeader)
h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="%s"; filename="%s"`, f.key, f.fileName))
h.Set("Content-Type", f.contentType)
formWriter, err := w.CreatePart(h)
if err != nil {
return bytes.Buffer{}, "", err
}
if _, err := io.Copy(formWriter, f.content); err != nil {
return bytes.Buffer{}, "", err
}
if x, ok := f.content.(io.Closer); ok {
_ = x.Close()
}
}
if err := w.Close(); err != nil {
return bytes.Buffer{}, "", err
}
return b, w.FormDataContentType(), nil
}
func (rs *RenderingService) sanitizeViaHTTP(ctx context.Context, req *SanitizeSVGRequest) (*SanitizeSVGResponse, error) {
sanitizerUrl, err := url.Parse(rs.sanitizeURL)
if err != nil {
return nil, err
}
configJson, err := json.Marshal(map[string]any{
"config": domPurifySvgConfig,
"configType": domPurifyConfigType,
})
if err != nil {
rs.log.Error("Sanitizer - HTTP: failed to create the request config", "error", err, "filename", req.Filename)
return nil, fmt.Errorf("config creation fail: %s", err)
}
body, contentType, err := createMultipartRequestBody([]formFile{
{
fileName: "config",
key: "config",
contentType: "application/json",
content: bytes.NewReader(configJson),
},
{
fileName: req.Filename,
key: "file",
contentType: "image/svg+xml",
content: bytes.NewReader(req.Content),
},
})
if err != nil {
rs.log.Error("Sanitizer - HTTP: failed to create the request body", "error", err, "filename", req.Filename)
return nil, fmt.Errorf("body creation fail: %s", err)
}
reqContext, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
httpReq, err := http.NewRequestWithContext(reqContext, "POST", sanitizerUrl.String(), &body)
if err != nil {
rs.log.Error("Sanitizer - HTTP: failed to create the HTTP request", "error", err, "filename", req.Filename)
return nil, err
}
httpReq.Header.Set("User-Agent", fmt.Sprintf("Grafana/%s", rs.Cfg.BuildVersion))
httpReq.Header.Set("Content-Type", contentType)
rs.log.Debug("Sanitizer - HTTP: calling", "filename", req.Filename, "contentLength", len(req.Content), "url", sanitizerUrl)
// make request to renderer server
resp, err := netClient.Do(httpReq)
if err != nil {
rs.log.Error("Sanitizer - HTTP: failed to send request", "error", err)
return nil, fmt.Errorf("sanitizer - HTTP: failed to send request: %w", err)
}
defer func() {
if err := resp.Body.Close(); err != nil {
rs.log.Error("Sanitizer - HTTP: failed to close response body", "statusCode", resp.StatusCode, "error", err)
}
}()
if resp.StatusCode != http.StatusOK {
if body, err := io.ReadAll(resp.Body); body != nil {
rs.log.Error("Sanitizer - HTTP: failed to sanitize", "statusCode", resp.StatusCode, "error", err, "resp", string(body))
} else {
rs.log.Error("Sanitizer - HTTP: failed to sanitize", "statusCode", resp.StatusCode, "error", err)
}
return nil, fmt.Errorf("sanitizer - HTTP: failed to sanitize %s", req.Filename)
}
sanitized, err := io.ReadAll(resp.Body)
if err != nil {
rs.log.Error("Sanitizer - HTTP: failed to read response body", "error", err, "filename", req.Filename)
return nil, fmt.Errorf("sanitizer - HTTP: failed to read response body: %s", err)
}
return &SanitizeSVGResponse{Sanitized: sanitized}, nil
}
func (rs *RenderingService) sanitizeSVGViaPlugin(ctx context.Context, req *SanitizeSVGRequest) (*SanitizeSVGResponse, error) {
ctx, cancel := context.WithTimeout(ctx, time.Second*20)
defer cancel()
domPurifyConfig, err := json.Marshal(domPurifySvgConfig)
if err != nil {
rs.log.Error("Sanitizer - plugin: failed to parse domPurifyConfig")
return nil, fmt.Errorf("sanitizer - plugin: failed to parse domPurifyConfig %s", err)
}
grpcReq := &pluginextensionv2.SanitizeRequest{
Filename: req.Filename,
Content: req.Content,
ConfigType: domPurifyConfigType,
Config: domPurifyConfig,
}
rs.log.Debug("Sanitizer - plugin: calling", "filename", req.Filename, "contentLength", len(req.Content))
rc, err := rs.plugin.Client()
if err != nil {
return nil, err
}
rsp, err := rc.Sanitize(ctx, grpcReq)
if err != nil {
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
rs.log.Info("Sanitizer - plugin: time out")
return nil, ErrTimeout
}
return nil, err
}
if rsp.Error != "" {
return nil, fmt.Errorf("sanitizer - plugin: failed to sanitize: %s", rsp.Error)
}
return &SanitizeSVGResponse{Sanitized: rsp.Sanitized}, nil
}
+1 -1
View File
@@ -5,11 +5,11 @@ import (
"fmt"
"testing"
"github.com/golang/mock/gomock"
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/org"
+6 -29
View File
@@ -2,46 +2,23 @@ package store
import (
"context"
"errors"
"mime"
"path/filepath"
"github.com/grafana/grafana/pkg/infra/filestorage"
"github.com/grafana/grafana/pkg/services/rendering"
"github.com/grafana/grafana/pkg/services/store/sanitizer"
"github.com/grafana/grafana/pkg/services/user"
)
func (s *standardStorageService) sanitizeContents(ctx context.Context, user *user.SignedInUser, req *UploadRequest, storagePath string) ([]byte, error) {
func (s *standardStorageService) sanitizeUploadRequest(ctx context.Context, user *user.SignedInUser, req *UploadRequest, storagePath string) (*filestorage.UpsertFileCommand, error) {
if req.EntityType == EntityTypeImage {
ext := filepath.Ext(req.Path)
if ext == ".svg" {
resp, err := sanitizer.SanitizeSVG(ctx, &rendering.SanitizeSVGRequest{
Filename: storagePath,
Content: req.Contents,
})
if err != nil {
if s.cfg != nil && s.cfg.AllowUnsanitizedSvgUpload {
grafanaStorageLogger.Debug("Allowing unsanitized svg upload", "filename", req.Path, "sanitizationError", err)
return req.Contents, nil
} else {
grafanaStorageLogger.Debug("Disallowing unsanitized svg upload", "filename", req.Path, "sanitizationError", err)
return nil, err
}
}
return resp.Sanitized, nil
if ext == ".svg" && !s.cfg.AllowUnsanitizedSvgUpload {
grafanaStorageLogger.Debug("Disallowing svg upload", "filename", req.Path)
return nil, errors.New("SVG uploads are not allowed")
}
}
return req.Contents, nil
}
func (s *standardStorageService) sanitizeUploadRequest(ctx context.Context, user *user.SignedInUser, req *UploadRequest, storagePath string) (*filestorage.UpsertFileCommand, error) {
contents, err := s.sanitizeContents(ctx, user, req, storagePath)
if err != nil {
return nil, err
}
// we have already validated that the file contents match the extension in `./validate.go`
mimeType := mime.TypeByExtension(filepath.Ext(req.Path))
if mimeType == "" {
@@ -51,7 +28,7 @@ func (s *standardStorageService) sanitizeUploadRequest(ctx context.Context, user
return &filestorage.UpsertFileCommand{
Path: storagePath,
Contents: contents,
Contents: req.Contents,
MimeType: mimeType,
CacheControl: req.CacheControl,
ContentDisposition: req.ContentDisposition,
-23
View File
@@ -1,23 +0,0 @@
package sanitizer
import (
"context"
"errors"
"github.com/grafana/grafana/pkg/services/rendering"
)
// workaround for cyclic dep between the store and the renderer
type Provider struct{}
var SanitizeSVG = func(ctx context.Context, req *rendering.SanitizeSVGRequest) (*rendering.SanitizeSVGResponse, error) {
return nil, errors.New("not implemented")
}
func ProvideService(
renderer rendering.Service,
) *Provider {
SanitizeSVG = renderer.SanitizeSVG
return &Provider{}
}