Skip to content

Commit

Permalink
implement share handling for accepting and listing shares
Browse files Browse the repository at this point in the history
Signed-off-by: David Christofas <dchristofas@owncloud.com>
  • Loading branch information
David Christofas committed Jul 8, 2020
1 parent 223eaed commit 30c1c42
Showing 1 changed file with 252 additions and 23 deletions.
275 changes: 252 additions & 23 deletions pkg/storage/fs/owncloud/owncloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,12 +152,13 @@ func init() {
}

type config struct {
DataDirectory string `mapstructure:"datadirectory"`
UploadInfoDir string `mapstructure:"upload_info_dir"`
UserLayout string `mapstructure:"user_layout"`
Redis string `mapstructure:"redis"`
EnableHome bool `mapstructure:"enable_home"`
Scan bool `mapstructure:"scan"`
DataDirectory string `mapstructure:"datadirectory"`
UploadInfoDir string `mapstructure:"upload_info_dir"`
ShareDirectory string `mapstructure:"sharedirectory"`
UserLayout string `mapstructure:"user_layout"`
Redis string `mapstructure:"redis"`
EnableHome bool `mapstructure:"enable_home"`
Scan bool `mapstructure:"scan"`
}

func parseConfig(m map[string]interface{}) (*config, error) {
Expand All @@ -179,6 +180,9 @@ func (c *config) init(m map[string]interface{}) {
if c.UploadInfoDir == "" {
c.UploadInfoDir = "/var/tmp/reva/uploadinfo"
}
if c.ShareDirectory == "" {
c.ShareDirectory = "/Shares"
}
// default to scanning if not configured
if _, ok := m["scan"]; !ok {
c.Scan = true
Expand Down Expand Up @@ -313,7 +317,37 @@ func (fs *ocfs) wrap(ctx context.Context, fn string) (internal string) {
return
}

// owncloud stores versions in the files_versions subfolder
func (fs *ocfs) wrapShadow(ctx context.Context, fn string) (internal string) {
if fs.c.EnableHome {
u := user.ContextMustGetUser(ctx)
layout := templates.WithUser(u, fs.c.UserLayout)
internal = path.Join(fs.c.DataDirectory, layout, "shadow_files", fn)
} else {
// trim all /
fn = strings.Trim(fn, "/")
// p = "" or
// p = <username> or
// p = <username>/foo/bar.txt
parts := strings.SplitN(fn, "/", 2)

switch len(parts) {
case 1:
// parts = "" or "<username>"
if parts[0] == "" {
internal = fs.c.DataDirectory
return
}
// parts = "<username>"
internal = path.Join(fs.c.DataDirectory, parts[0], "shadow_files")
default:
// parts = "<username>", "foo/bar.txt"
internal = path.Join(fs.c.DataDirectory, parts[0], "shadow_files", parts[1])
}
}
return
}

// ownloud stores versions in the files_versions subfolder
// the incoming path starts with /<username>, so we need to insert the files subfolder into the path
// and prefix the data directory
// TODO the path handed to a storage provider should not contain the username
Expand Down Expand Up @@ -365,6 +399,10 @@ func (fs *ocfs) unwrap(ctx context.Context, internal string) (external string) {
layout := templates.WithUser(u, fs.c.UserLayout)
trim := path.Join(fs.c.DataDirectory, layout, "files")
external = strings.TrimPrefix(internal, trim)
// root directory
if external == "" {
external = "/"
}
} else {
// np = /data/<username>/files/foo/bar.txt
// remove data dir
Expand Down Expand Up @@ -392,6 +430,39 @@ func (fs *ocfs) unwrap(ctx context.Context, internal string) (external string) {
return
}

func (fs *ocfs) unwrapShadow(ctx context.Context, internal string) (external string) {
if fs.c.EnableHome {
u := user.ContextMustGetUser(ctx)
layout := templates.WithUser(u, fs.c.UserLayout)
trim := path.Join(fs.c.DataDirectory, layout, "shadow_files")
external = strings.TrimPrefix(internal, trim)
} else {
// np = /data/<username>/shadow_files/foo/bar.txt
// remove data dir
if fs.c.DataDirectory != "/" {
// fs.c.DataDirectory is a clean path, so it never ends in /
internal = strings.TrimPrefix(internal, fs.c.DataDirectory)
// np = /<username>/shadow_files/foo/bar.txt
}

parts := strings.SplitN(internal, "/", 4)
// parts = "", "<username>", "shadow_files", "foo/bar.txt"
switch len(parts) {
case 1:
external = "/"
case 2:
external = path.Join("/", parts[1])
case 3:
external = path.Join("/", parts[1])
default:
external = path.Join("/", parts[1], parts[3])
}
}
log := appctx.GetLogger(ctx)
log.Debug().Msgf("ocfs: unwrapShadow: internal=%s external=%s", internal, external)
return
}

// TODO the owner needs to come from a different place
func (fs *ocfs) getOwner(internal string) string {
internal = strings.TrimPrefix(internal, fs.c.DataDirectory)
Expand All @@ -402,9 +473,8 @@ func (fs *ocfs) getOwner(internal string) string {
return ""
}

func (fs *ocfs) convertToResourceInfo(ctx context.Context, fi os.FileInfo, np string, c redis.Conn, mdKeys []string) *provider.ResourceInfo {
func (fs *ocfs) convertToResourceInfo(ctx context.Context, fi os.FileInfo, np string, fn string, c redis.Conn, mdKeys []string) *provider.ResourceInfo {
id := readOrCreateID(ctx, np, c)
fn := fs.unwrap(ctx, path.Join("/", np))

etag := calcEtag(ctx, fi)

Expand Down Expand Up @@ -937,6 +1007,7 @@ func (fs *ocfs) CreateHome(ctx context.Context) error {
path.Join(fs.c.DataDirectory, layout, "files_trashbin"),
path.Join(fs.c.DataDirectory, layout, "files_versions"),
path.Join(fs.c.DataDirectory, layout, "uploads"),
path.Join(fs.c.DataDirectory, layout, "shadow_files"),
}

for _, v := range homePaths {
Expand Down Expand Up @@ -968,9 +1039,36 @@ func (fs *ocfs) CreateDir(ctx context.Context, fn string) (err error) {
return fs.propagate(ctx, np)
}

func (fs *ocfs) CreateReference(ctx context.Context, path string, targetURI *url.URL) error {
// TODO(jfd): implement
return errtypes.NotSupported("ocfs: operation not supported")
func (fs *ocfs) isShareFolderChild(p string) bool {
return strings.HasPrefix(p, fs.c.ShareDirectory)
}

func (fs *ocfs) isShareFolderRoot(p string) bool {
return p == fs.c.ShareDirectory
}

func (fs *ocfs) CreateReference(ctx context.Context, p string, targetURI *url.URL) error {
if !fs.isShareFolderChild(p) {
return errtypes.PermissionDenied("ocfs: cannot create references outside the share folder: share_folder=" + "/Shares" + " path=" + p)
}

fn := fs.wrapShadow(ctx, p)

dir, _ := path.Split(fn)
if err := os.MkdirAll(dir, 0700); err != nil {
return errors.Wrapf(err, "ocfs: error creating shadow path %s", dir)
}

f, err := os.Create(fn)
if err != nil {
return errors.Wrapf(err, "ocfs: error creating shadow file %s", fn)
}

err = xattr.FSet(f, mdPrefix+"target", []byte(targetURI.String()))
if err != nil {
return errors.Wrapf(err, "ocfs: error setting the target %s on the shadow file %s", targetURI.String(), fn)
}
return nil
}

func (fs *ocfs) setMtime(ctx context.Context, np string, mtimeString string) error {
Expand Down Expand Up @@ -1314,7 +1412,19 @@ func (fs *ocfs) GetMD(ctx context.Context, ref *provider.Reference, mdKeys []str
if err != nil {
return nil, errors.Wrap(err, "ocfs: error resolving reference")
}
p := fs.unwrap(ctx, np)

if fs.c.EnableHome {
if fs.isShareFolderChild(p) {
return fs.getMDShareFolder(ctx, p, mdKeys)
}
}

// If GetMD is called for a path shared with the user then the path is
// already wrapped. (fs.resolve wraps the path)
if strings.HasPrefix(p, fs.c.DataDirectory) {
np = p
}
md, err := os.Stat(np)
if err != nil {
if os.IsNotExist(err) {
Expand All @@ -1324,34 +1434,153 @@ func (fs *ocfs) GetMD(ctx context.Context, ref *provider.Reference, mdKeys []str
}
c := fs.pool.Get()
defer c.Close()
m := fs.convertToResourceInfo(ctx, md, np, c, mdKeys)
m := fs.convertToResourceInfo(ctx, md, np, fs.unwrap(ctx, np), c, mdKeys)

return m, nil
}

func (fs *ocfs) getMDShareFolder(ctx context.Context, p string, mdKeys []string) (*provider.ResourceInfo, error) {
fn := fs.wrapShadow(ctx, p)
md, err := os.Stat(fn)
if err != nil {
if os.IsNotExist(err) {
return nil, errtypes.NotFound(fs.unwrapShadow(ctx, fn))
}
return nil, errors.Wrapf(err, "ocfs: error stating %s", fn)
}
c := fs.pool.Get()
defer c.Close()
m := fs.convertToResourceInfo(ctx, md, fn, fs.unwrapShadow(ctx, fn), c, mdKeys)
if !fs.isShareFolderRoot(p) {
m.Type = provider.ResourceType_RESOURCE_TYPE_REFERENCE
ref, err := xattr.Get(fn, mdPrefix+"target")
if err != nil {
return nil, err
}
m.Target = string(ref)
}

return m, nil
}

func (fs *ocfs) ListFolder(ctx context.Context, ref *provider.Reference, mdKeys []string) ([]*provider.ResourceInfo, error) {
log := appctx.GetLogger(ctx)

np, err := fs.resolve(ctx, ref)
if err != nil {
return nil, errors.Wrap(err, "ocfs: error resolving reference")
}
p := fs.unwrap(ctx, np)

mds, err := ioutil.ReadDir(np)
if err != nil {
if os.IsNotExist(err) {
return nil, errtypes.NotFound(fs.unwrap(ctx, np))
if fs.c.EnableHome {
log.Debug().Msg("home enabled")
if strings.HasPrefix(p, "/") {
return fs.listWithHome(ctx, "/", p, mdKeys)
}
return nil, errors.Wrap(err, "ocfs: error listing "+np)
}

finfos := make([]*provider.ResourceInfo, 0, len(mds))
// TODO we should only open a connection if we need to set / store the fileid. no need to always open a connection when listing files
log.Debug().Msg("list with nominal home")
return fs.listWithNominalHome(ctx, p, mdKeys)
}

func (fs *ocfs) listWithNominalHome(ctx context.Context, p string, mdKeys []string) ([]*provider.ResourceInfo, error) {
fn := p
// If a user wants to list a folder shared with him the path will already
// be wrapped with the files directory path of the share owner.
// In that case we don't want to wrap the path again.
if !strings.HasPrefix(p, fs.c.DataDirectory) {
fn = fs.wrap(ctx, p)
}
mds, err := ioutil.ReadDir(fn)
if err != nil {
return nil, errors.Wrapf(err, "ocfs: error listing %s", fn)
}
c := fs.pool.Get()
defer c.Close()
for i := range mds {
p := path.Join(np, mds[i].Name())
m := fs.convertToResourceInfo(ctx, mds[i], p, c, mdKeys)
finfos := []*provider.ResourceInfo{}
for _, md := range mds {
p := path.Join(fn, md.Name())
m := fs.convertToResourceInfo(ctx, md, p, fs.unwrap(ctx, p), c, mdKeys)
finfos = append(finfos, m)
}
return finfos, nil
}

func (fs *ocfs) listWithHome(ctx context.Context, home, p string, mdKeys []string) ([]*provider.ResourceInfo, error) {
log := appctx.GetLogger(ctx)
if p == home {
log.Debug().Msg("listing home")
return fs.listHome(ctx, home, mdKeys)
}

if fs.isShareFolderRoot(p) {
log.Debug().Msg("listing share folder root")
return fs.listShareFolderRoot(ctx, p, mdKeys)
}

if fs.isShareFolderChild(p) {
return nil, errtypes.PermissionDenied("ocfs: error listing folders inside the shared folder, only file references are stored inside")
}

log.Debug().Msg("listing nominal home")
return fs.listWithNominalHome(ctx, p, mdKeys)
}

func (fs *ocfs) listHome(ctx context.Context, home string, mdKeys []string) ([]*provider.ResourceInfo, error) {
// list files
fn := fs.wrap(ctx, home)
mds, err := ioutil.ReadDir(fn)
if err != nil {
return nil, errors.Wrap(err, "ocfs: error listing files")
}

c := fs.pool.Get()
defer c.Close()

finfos := []*provider.ResourceInfo{}
for _, md := range mds {
p := path.Join(fn, md.Name())
m := fs.convertToResourceInfo(ctx, md, p, fs.unwrap(ctx, p), c, mdKeys)
finfos = append(finfos, m)
}

// list shadow_files
fn = fs.wrapShadow(ctx, home)
mds, err = ioutil.ReadDir(fn)
if err != nil {
return nil, errors.Wrap(err, "ocfs: error listing shadow_files")
}
for _, md := range mds {
p := path.Join(fn, md.Name())
m := fs.convertToResourceInfo(ctx, md, p, fs.unwrapShadow(ctx, p), c, mdKeys)
finfos = append(finfos, m)
}
return finfos, nil
}

func (fs *ocfs) listShareFolderRoot(ctx context.Context, p string, mdKeys []string) ([]*provider.ResourceInfo, error) {
fn := fs.wrapShadow(ctx, p)
mds, err := ioutil.ReadDir(fn)
if err != nil {
return nil, errors.Wrap(err, "ocfs: error listing shadow_files")
}

c := fs.pool.Get()
defer c.Close()

finfos := []*provider.ResourceInfo{}
for _, md := range mds {
p := path.Join(fn, md.Name())
m := fs.convertToResourceInfo(ctx, md, p, fs.unwrapShadow(ctx, p), c, mdKeys)
m.Type = provider.ResourceType_RESOURCE_TYPE_REFERENCE
ref, err := xattr.Get(p, mdPrefix+"target")
if err != nil {
return nil, err
}
m.Target = string(ref)
finfos = append(finfos, m)
}

return finfos, nil
}

Expand Down

0 comments on commit 30c1c42

Please sign in to comment.