limit thumbnails a user can access to his own.

This commit is contained in:
David Christofas
2020-05-27 13:17:10 +02:00
parent 6763455741
commit 4ae1a64376
9 changed files with 163 additions and 54 deletions
+6 -5
View File
@@ -66,11 +66,12 @@ func TestGetThumbnailInvalidImage(t *testing.T) {
func TestGetThumbnail(t *testing.T) {
req := proto.GetRequest{
Filepath: "oc.png",
Filetype: proto.GetRequest_PNG,
Etag: "33a64df551425fcc55e4d42a148795d9f25f89d4",
Height: 32,
Width: 32,
Filepath: "oc.png",
Filetype: proto.GetRequest_PNG,
Etag: "33a64df551425fcc55e4d42a148795d9f25f89d4",
Height: 32,
Width: 32,
Authorization: "Bearer eyJhbGciOiJQUzI1NiIsImtpZCI6IiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJwaG9lbml4IiwiZXhwIjoxNTkwNTc1Mzk4LCJqdGkiOiJqUEw5c1A3UUEzY0diYi1yRnhkSjJCWnFPc1BDTDg1ZyIsImlhdCI6MTU5MDU3NDc5OCwiaXNzIjoiaHR0cHM6Ly9sb2NhbGhvc3Q6OTIwMCIsInN1YiI6Ilh0U2lfbWl5V1NCLXBrdkdueFBvQzVBNGZsaWgwVUNMZ3ZVN2NMd2ptakNLWDdGWW4ySFdrNnJSQ0V1eTJHNXFBeV95TVFjX0ZLOWFORmhVTXJYMnBRQGtvbm5lY3QiLCJrYy5pc0FjY2Vzc1Rva2VuIjp0cnVlLCJrYy5hdXRob3JpemVkU2NvcGVzIjpbIm9wZW5pZCIsInByb2ZpbGUiLCJlbWFpbCJdLCJrYy5pZGVudGl0eSI6eyJrYy5pLmRuIjoiRWluc3RlaW4iLCJrYy5pLmlkIjoiY249ZWluc3RlaW4sb3U9dXNlcnMsZGM9ZXhhbXBsZSxkYz1vcmciLCJrYy5pLnVuIjoiZWluc3RlaW4ifSwia2MucHJvdmlkZXIiOiJpZGVudGlmaWVyLWxkYXAifQ.FSDe4vzwYpHbNfckBON5EI-01MS_dYFxenddqfJPzjlAEMEH2FFn2xQHCsxhC7wSxivhjV7Z5eRoNUR606keA64Tjs8pJBNECSptBMmE_xfAlc6X5IFILgDnR5bBu6Z2hhu-dVj72Hcyvo_X__OeWekYu7oyoXW41Mw3ayiUAwjCAzV3WPOAJ_r0zbW68_m29BgH3BoSxaF6lmjStIIAIyw7IBZ2QXb_FvGouknmfeWlGL9lkFPGL_dYKwjWieG947nY4Kg8IvHByEbw-xlY3L2EdA7Q8ZMbqdX7GzjtEIVYvCT4-TxWRcmB3SmO-Z8CVq27NHlKm3aZ0k2PS8Ga1w",
}
client := service.Client()
cl := proto.NewThumbnailService("com.owncloud.api.thumbnails", client)
+33 -1
View File
@@ -4,11 +4,14 @@ import (
"context"
"fmt"
"gopkg.in/square/go-jose.v2/jwt"
"github.com/owncloud/ocis-pkg/v2/log"
v0proto "github.com/owncloud/ocis-thumbnails/pkg/proto/v0"
"github.com/owncloud/ocis-thumbnails/pkg/thumbnail"
"github.com/owncloud/ocis-thumbnails/pkg/thumbnail/imgsource"
"github.com/owncloud/ocis-thumbnails/pkg/thumbnail/resolution"
"github.com/pkg/errors"
)
// NewService returns a service implementation for Service.
@@ -47,11 +50,22 @@ func (g Thumbnail) GetThumbnail(ctx context.Context, req *v0proto.GetRequest, rs
return fmt.Errorf("can't be encoded. filetype %s not supported", req.Filetype.String())
}
r := g.resolutions.ClosestMatch(int(req.Width), int(req.Height))
auth := req.Authorization
if auth == "" {
return fmt.Errorf("authorization is missing")
}
username, err := usernameFromAuthorization(auth)
if err != nil {
return err
}
tr := thumbnail.Request{
Resolution: r,
ImagePath: req.Filepath,
Encoder: encoder,
ETag: req.Etag,
Username: username,
}
thumbnail := g.manager.GetStored(tr)
@@ -61,7 +75,6 @@ func (g Thumbnail) GetThumbnail(ctx context.Context, req *v0proto.GetRequest, rs
return nil
}
auth := req.Authorization
sCtx := imgsource.WithAuthorization(ctx, auth)
img, err := g.source.Get(sCtx, tr.ImagePath)
if err != nil {
@@ -79,3 +92,22 @@ func (g Thumbnail) GetThumbnail(ctx context.Context, req *v0proto.GetRequest, rs
rsp.Mimetype = tr.Encoder.MimeType()
return nil
}
func usernameFromAuthorization(auth string) (string, error) {
tokenString := auth[len("Bearer "):] // strip the bearer prefix
var claims map[string]interface{}
token, err := jwt.ParseSigned(tokenString)
if err != nil {
return "", errors.Wrap(err, "could not parse auth token")
}
err = token.UnsafeClaimsWithoutVerification(&claims)
if err != nil {
return "", errors.Wrap(err, "could not get claims from auth token")
}
identityMap := claims["kc.identity"].(map[string]interface{})
username := identityMap["kc.i.un"].(string)
return username, nil
}
+95 -38
View File
@@ -1,64 +1,62 @@
package storage
import (
"bytes"
"fmt"
"crypto/sha256"
"encoding/hex"
"io/ioutil"
"os"
"path/filepath"
"strings"
"sync"
"github.com/owncloud/ocis-pkg/v2/log"
"github.com/owncloud/ocis-thumbnails/pkg/config"
"github.com/pkg/errors"
)
const (
usersDir = "users"
filesDir = "files"
)
// NewFileSystemStorage creates a new instanz of FileSystem
func NewFileSystemStorage(cfg config.FileSystemStorage, logger log.Logger) FileSystem {
return FileSystem{
dir: cfg.RootDirectory,
func NewFileSystemStorage(cfg config.FileSystemStorage, logger log.Logger) *FileSystem {
return &FileSystem{
root: cfg.RootDirectory,
logger: logger,
}
}
// FileSystem represents a storage for the thumbnails using the local file system.
type FileSystem struct {
dir string
root string
logger log.Logger
mux sync.Mutex
}
// Get loads the image from the file system.
func (s FileSystem) Get(key string) []byte {
path := filepath.Join(s.dir, key)
content, err := ioutil.ReadFile(filepath.Clean(path))
func (s *FileSystem) Get(username string, key string) []byte {
userDir := s.userDir(username)
img := filepath.Join(userDir, key)
content, err := ioutil.ReadFile(img)
if err != nil {
s.logger.Warn().Err(err).Msgf("could not read file %s", key)
return nil
}
return content
}
// Set writes the image to the file system.
func (s FileSystem) Set(key string, img []byte) error {
path := filepath.Join(s.dir, key)
dir := filepath.Dir(path)
if err := os.MkdirAll(dir, 0700); err != nil {
return fmt.Errorf("error while creating directory %s", dir)
}
f, err := os.Create(path)
func (s *FileSystem) Set(username string, key string, img []byte) error {
_, err := s.storeImage(key, img)
if err != nil {
return fmt.Errorf("could not create file \"%s\" error: %s", key, err.Error())
return errors.Wrap(err, "could not store image")
}
defer func() {
if err := f.Close(); err != nil {
s.logger.Warn().Err(err).Msg("closing file resulted in an error")
}
}()
_, err = f.Write(img)
userDir, err := s.createUserDir(username)
if err != nil {
return fmt.Errorf("could not write to file \"%s\" error: %s", key, err.Error())
return err
}
return nil
return s.linkImageToUserDir(key, userDir)
}
// BuildKey generate the unique key for a thumbnail.
@@ -69,19 +67,78 @@ func (s FileSystem) Set(key string, img []byte) error {
// e.g. 97/9f/4c8db98f7b82e768ef478d3c8612/500x300.png
//
// The key also represents the path to the thumbnail in the filesystem under the configured root directory.
func (s FileSystem) BuildKey(r Request) string {
func (s *FileSystem) BuildKey(r Request) string {
etag := r.ETag
filetype := r.Types[0]
filename := r.Resolution.String() + "." + filetype
key := new(bytes.Buffer)
key.WriteString(etag[:2])
key.WriteRune('/')
key.WriteString(etag[2:4])
key.WriteRune('/')
key.WriteString(etag[4:])
key.WriteRune('/')
key.WriteString(filename)
return key.String()
return filepath.Join(etag[:2], etag[2:4], etag[4:], filename)
}
func (s *FileSystem) rootDir(key string) string {
p := strings.Split(key, string(os.PathSeparator))
return p[0]
}
func (s *FileSystem) storeImage(key string, img []byte) (string, error) {
s.mux.Lock()
defer s.mux.Unlock()
imgPath := filepath.Join(s.root, filesDir, key)
dir := filepath.Dir(imgPath)
if err := os.MkdirAll(dir, 0700); err != nil {
return "", errors.Wrapf(err, "error while creating directory %s", dir)
}
if _, err := os.Stat(imgPath); os.IsNotExist(err) {
f, err := os.Create(imgPath)
if err != nil {
return "", errors.Wrapf(err, "could not create file \"%s\"", key)
}
defer f.Close()
_, err = f.Write(img)
if err != nil {
return "", errors.Wrapf(err, "could not write to file \"%s\"", key)
}
}
return imgPath, nil
}
// userDir returns the path to the user directory.
// The username is hashed before appending it on the path to prevent bugs caused by invalid folder names.
// Also the hash is then splitted up in three parts that results in a path which looks as follows:
// <filestorage-root>/users/<3 characters>/<3 characters>/<48 characters>/
// This will balance the folders in setups with many users.
func (s *FileSystem) userDir(username string) string {
hash := sha256.New224()
hash.Write([]byte(username))
unHash := hex.EncodeToString(hash.Sum(nil)) // 224 Bits or 224 / 4 = 56 characters.
return filepath.Join(s.root, usersDir, unHash[:3], unHash[3:6], unHash[6:])
}
func (s *FileSystem) createUserDir(username string) (string, error) {
userDir := s.userDir(username)
if err := os.MkdirAll(userDir, 0700); err != nil {
return "", errors.Wrapf(err, "could not create userDir: %s", userDir)
}
return userDir, nil
}
// linkImageToUserDir links the stored images to the user directory.
// The goal is to minimize disk usage by linking to the images if they already exist and avoid file duplicaiton.
func (s *FileSystem) linkImageToUserDir(key string, userDir string) error {
imgRootDir := s.rootDir(key)
s.mux.Lock()
defer s.mux.Unlock()
err := os.Symlink(filepath.Join(s.root, filesDir, imgRootDir), filepath.Join(userDir, imgRootDir))
if err != nil {
if !os.IsExist(err) {
return errors.Wrap(err, "could not link image to userdir")
}
}
return nil
}
+13 -6
View File
@@ -7,24 +7,31 @@ import (
// NewInMemoryStorage creates a new InMemory instance.
func NewInMemoryStorage() InMemory {
return InMemory{
store: make(map[string][]byte),
store: make(map[string]map[string][]byte),
}
}
// InMemory represents an in memory storage for thumbnails
// Can be used during development
type InMemory struct {
store map[string][]byte
store map[string]map[string][]byte
}
// Get loads the thumbnail from memory.
func (s InMemory) Get(key string) []byte {
return s.store[key]
func (s InMemory) Get(username string, key string) []byte {
userImages := s.store[username]
if userImages == nil {
return nil
}
return s.store[username][key]
}
// Set stores the thumbnail in memory.
func (s InMemory) Set(key string, thumbnail []byte) error {
s.store[key] = thumbnail
func (s InMemory) Set(username string, key string, thumbnail []byte) error {
if _, ok := s.store[username]; !ok {
s.store[username] = make(map[string][]byte)
}
s.store[username][key] = thumbnail
return nil
}
+2 -2
View File
@@ -11,7 +11,7 @@ type Request struct {
// Storage defines the interface for a thumbnail store.
type Storage interface {
Get(string) []byte
Set(string, []byte) error
Get(string, string) []byte
Set(string, string, []byte) error
BuildKey(Request) string
}
+3 -2
View File
@@ -16,6 +16,7 @@ type Request struct {
ImagePath string
Encoder Encoder
ETag string
Username string
}
// Manager is responsible for generating thumbnails
@@ -53,7 +54,7 @@ func (s SimpleManager) Get(r Request, img image.Image) ([]byte, error) {
return nil, err
}
bytes := buf.Bytes()
err = s.storage.Set(key, bytes)
err = s.storage.Set(r.Username, key, bytes)
if err != nil {
s.logger.Warn().Err(err).Msg("could not store thumbnail")
}
@@ -64,7 +65,7 @@ func (s SimpleManager) Get(r Request, img image.Image) ([]byte, error) {
// If there is no cached thumbnail it will return nil
func (s SimpleManager) GetStored(r Request) []byte {
key := s.storage.BuildKey(mapToStorageRequest(r))
stored := s.storage.Get(key)
stored := s.storage.Get(r.Username, key)
return stored
}