Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implement share handling for accepting and listing folder shares #929

Merged
merged 1 commit into from
Jul 9, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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