mirror of https://github.com/grafana/grafana
Storage: validation and sanitization stubs (#50523)
* add `IsPathValidationError` util to fs api * refactor storage.Upload method * remove unused struct * extract `RootUpload` constant * move file validation outside of the service * Make UploadErrorToStatusCode exported * validation/sanitization * refactor pathValidationError check * refactor, rename sanitize to transform * add a todo * refactor * transform -> sanitize * lint fix * #50608: fix jpg/jpeg Co-authored-by: Tania B <yalyna.ts@gmail.com> Co-authored-by: Ryan McKinley <ryantxu@gmail.com>pull/50852/head
parent
dfb0f6b1b8
commit
cc4473faf3
@ -0,0 +1,28 @@ |
||||
package store |
||||
|
||||
import ( |
||||
"context" |
||||
"path/filepath" |
||||
|
||||
"github.com/grafana/grafana/pkg/infra/filestorage" |
||||
"github.com/grafana/grafana/pkg/models" |
||||
) |
||||
|
||||
func (s *standardStorageService) sanitizeUploadRequest(ctx context.Context, user *models.SignedInUser, req *UploadRequest, storagePath string) (*filestorage.UpsertFileCommand, error) { |
||||
if req.EntityType == EntityTypeImage { |
||||
ext := filepath.Ext(req.Path) |
||||
//nolint: staticcheck
|
||||
if ext == ".svg" { |
||||
// TODO: sanitize svg
|
||||
} |
||||
} |
||||
|
||||
return &filestorage.UpsertFileCommand{ |
||||
Path: storagePath, |
||||
Contents: req.Contents, |
||||
MimeType: req.MimeType, |
||||
CacheControl: req.CacheControl, |
||||
ContentDisposition: req.ContentDisposition, |
||||
Properties: req.Properties, |
||||
}, nil |
||||
} |
@ -0,0 +1,90 @@ |
||||
package store |
||||
|
||||
import ( |
||||
"context" |
||||
"encoding/json" |
||||
"path/filepath" |
||||
|
||||
"github.com/grafana/grafana/pkg/infra/filestorage" |
||||
"github.com/grafana/grafana/pkg/models" |
||||
) |
||||
|
||||
var ( |
||||
allowedImageExtensions = map[string]bool{ |
||||
".jpg": true, |
||||
".jpeg": true, |
||||
".gif": true, |
||||
".png": true, |
||||
".webp": true, |
||||
} |
||||
imageExtensionsToMatchingMimeTypes = map[string]map[string]bool{ |
||||
".jpg": {"image/jpg": true, "image/jpeg": true}, |
||||
".jpeg": {"image/jpg": true, "image/jpeg": true}, |
||||
".gif": {"image/gif": true}, |
||||
".png": {"image/png": true}, |
||||
".webp": {"image/webp": true}, |
||||
} |
||||
) |
||||
|
||||
type validationResult struct { |
||||
ok bool |
||||
reason string |
||||
} |
||||
|
||||
func success() validationResult { |
||||
return validationResult{ |
||||
ok: true, |
||||
} |
||||
} |
||||
|
||||
func fail(reason string) validationResult { |
||||
return validationResult{ |
||||
ok: false, |
||||
reason: reason, |
||||
} |
||||
} |
||||
|
||||
func (s *standardStorageService) detectMimeType(ctx context.Context, user *models.SignedInUser, uploadRequest *UploadRequest) string { |
||||
// TODO: implement a spoofing-proof MimeType detection based on the contents
|
||||
return uploadRequest.MimeType |
||||
} |
||||
|
||||
func (s *standardStorageService) validateImage(ctx context.Context, user *models.SignedInUser, uploadRequest *UploadRequest) validationResult { |
||||
ext := filepath.Ext(uploadRequest.Path) |
||||
if !allowedImageExtensions[ext] { |
||||
return fail("unsupported extension") |
||||
} |
||||
|
||||
mimeType := s.detectMimeType(ctx, user, uploadRequest) |
||||
if !imageExtensionsToMatchingMimeTypes[ext][mimeType] { |
||||
return fail("mismatched extension and file contents") |
||||
} |
||||
|
||||
return success() |
||||
} |
||||
|
||||
func (s *standardStorageService) validateUploadRequest(ctx context.Context, user *models.SignedInUser, req *UploadRequest, storagePath string) validationResult { |
||||
// TODO: validateSize
|
||||
// TODO: validateProperties
|
||||
|
||||
if err := filestorage.ValidatePath(storagePath); err != nil { |
||||
return fail("path validation failed: " + err.Error()) |
||||
} |
||||
|
||||
switch req.EntityType { |
||||
case EntityTypeFolder: |
||||
fallthrough |
||||
case EntityTypeDashboard: |
||||
// TODO: add proper validation
|
||||
var something interface{} |
||||
if err := json.Unmarshal(req.Contents, &something); err != nil { |
||||
return fail(err.Error()) |
||||
} |
||||
|
||||
return success() |
||||
case EntityTypeImage: |
||||
return s.validateImage(ctx, user, req) |
||||
default: |
||||
return fail("unknown entity") |
||||
} |
||||
} |
Loading…
Reference in new issue