fix: enforce recursive WebDAV permissions
This commit is contained in:
@@ -829,6 +829,9 @@ func (f *webdavAuthFS) RemoveAll(ctx context.Context, name string, opts *webdav.
|
||||
if err := f.allow(name, auth.Delete); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := f.checkTree(ctx, name, auth.Delete); err != nil {
|
||||
return err
|
||||
}
|
||||
return f.inner.RemoveAll(ctx, name, opts)
|
||||
}
|
||||
func (f *webdavAuthFS) Mkdir(ctx context.Context, name string) error {
|
||||
@@ -845,6 +848,9 @@ func (f *webdavAuthFS) Copy(ctx context.Context, name, dest string, opts *webdav
|
||||
if err := f.allow(name, auth.Read); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := f.checkTree(ctx, name, auth.Read); err != nil {
|
||||
return false, err
|
||||
}
|
||||
parent := path.Dir(strings.Trim(dest, "/"))
|
||||
if parent == "." {
|
||||
parent = ""
|
||||
@@ -855,7 +861,10 @@ func (f *webdavAuthFS) Copy(ctx context.Context, name, dest string, opts *webdav
|
||||
return f.inner.Copy(ctx, name, dest, opts)
|
||||
}
|
||||
func (f *webdavAuthFS) Move(ctx context.Context, name, dest string, opts *webdav.MoveOptions) (bool, error) {
|
||||
if err := f.allow(name, auth.Write); err != nil {
|
||||
if err := f.allow(name, auth.Delete); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := f.checkTree(ctx, name, auth.Delete); err != nil {
|
||||
return false, err
|
||||
}
|
||||
parent := path.Dir(strings.Trim(dest, "/"))
|
||||
@@ -868,6 +877,27 @@ func (f *webdavAuthFS) Move(ctx context.Context, name, dest string, opts *webdav
|
||||
return f.inner.Move(ctx, name, dest, opts)
|
||||
}
|
||||
|
||||
func (f *webdavAuthFS) checkTree(ctx context.Context, name string, p auth.PermMask) error {
|
||||
info, err := f.inner.Stat(ctx, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !info.IsDir {
|
||||
return nil
|
||||
}
|
||||
items, err := f.inner.ReadDir(ctx, name, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, item := range items {
|
||||
itemPath := strings.Trim(item.Path, "/")
|
||||
if err := f.allow(itemPath, p); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) ipFilter(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if s.Allow != "" && !allowedIP(s.Allow, clientIP(r)) {
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"chfs-ng/internal/auth"
|
||||
"github.com/emersion/go-webdav"
|
||||
)
|
||||
|
||||
func TestWebDAVAuthFSCheckTree(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
if err := os.MkdirAll(filepath.Join(root, "test", "private"), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(root, "test", "public.txt"), []byte("public"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(root, "test", "private", "secret.txt"), []byte("secret"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
account := &auth.Account{Mask: auth.Delete, Rules: []auth.PathRule{
|
||||
{Dir: "test", Mask: auth.Delete},
|
||||
{Dir: "test/private", Mask: auth.None},
|
||||
}}
|
||||
fs := &webdavAuthFS{inner: webdav.LocalFileSystem(root), account: account}
|
||||
|
||||
if err := fs.checkTree(context.Background(), "/test", auth.Delete); err == nil {
|
||||
t.Fatal("expected recursive delete permission check to reject inaccessible descendant")
|
||||
}
|
||||
if err := fs.checkTree(context.Background(), "/test/public.txt", auth.Delete); err != nil {
|
||||
t.Fatalf("public file should be deletable: %v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user